@omfalos/mokosh 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +33 -31
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +33 -31
- package/dist/cli.mjs.map +1 -1
- package/dist/duplication-worker.d.mts +25 -0
- package/dist/duplication-worker.d.ts +25 -0
- package/dist/duplication-worker.js +5 -0
- package/dist/duplication-worker.js.map +1 -0
- package/dist/duplication-worker.mjs +5 -0
- package/dist/duplication-worker.mjs.map +1 -0
- package/dist/index.d.mts +41 -10
- package/dist/index.d.ts +41 -10
- package/dist/index.js +23 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -19
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.js +24 -22
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs +24 -22
- package/dist/mcp.mjs.map +1 -1
- package/dist/parse-CAKctgb6.d.mts +6 -0
- package/dist/parse-CAKctgb6.d.ts +6 -0
- package/dist/parse-worker.d.mts +2 -1
- package/dist/parse-worker.d.ts +2 -1
- package/dist/{types-C9fLCS45.d.mts → types-BlN-U5AM.d.ts} +2 -5
- package/dist/{types-C9fLCS45.d.ts → types-BtSqoqbZ.d.mts} +2 -5
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +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/duplication/index.ts","../src/parser/lockfile.ts","../src/graph/duplication/families.ts","../src/graph/duplication/shingle.ts","../src/parser/style/css.ts","../src/parser/style/scss.ts","../src/graph/duplication/style-blocks.ts","../src/graph/duplication/tokenizer.ts","../src/graph/features/index.ts","../src/graph/features/feature-graph.ts","../src/graph/language-support.ts","../src/graph/analyzer.ts","../src/graph/model.ts","../src/graph/symbol-traversal.ts","../src/graph/queries.ts","../src/graph/responsibility/infer-role.ts","../src/graph/responsibility/index.ts","../src/graph/symbol.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/graph/workspace-model.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/file-type.ts","../src/parser/lang/coffee.ts","../src/parser/utils.ts","../src/parser/lang/gherkin.ts","../src/parser/lang/go.ts","../src/parser/complexity/lezer-utils.ts","../src/parser/complexity/go.ts","../src/parser/lang/ls.ts","../src/parser/lang/lua.ts","../src/parser/lang/markdown.ts","../src/parser/lang/python.ts","../src/parser/complexity/python.ts","../src/parser/lang/typescript.ts","../src/parser/complexity.ts","../src/parser/tagging/index.ts","../src/parser/style/barrel.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/markdown.ts","../src/graph/lang-resolvers/python.ts","../src/graph/lang-resolvers/style.ts","../src/tags/proposer.ts","../src/index.ts","../src/cli/args.ts","../src/cli/const.ts","../src/cli/commands/affected.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/check-doc-drift.ts","../src/cli/commands/clear-cache.ts","../src/cli/commands/dependencies.ts","../src/cli/commands/dependents.ts","../src/cli/commands/detect-features.ts","../src/cli/commands/feature-graph.ts","../src/cli/commands/find-complex-functions.ts","../src/cli/commands/find-duplicates.ts","../src/cli/commands/find-symbol.ts","../src/cli/commands/find-uncovered.ts","../src/cli/commands/find-unused.ts","../src/cli/commands/graph-output.ts","../src/cli/commands/init-config.ts","../src/cli/commands/template-dir.ts","../src/cli/commands/init-skill.ts","../src/cli/commands/list-tags.ts","../src/cli/commands/module-responsibility.ts","../src/cli/commands/propose-tags.ts","../src/cli/commands/type-graph.ts","../src/cli/commands/workspace-affected.ts","../src/cli/commands/workspace-packages.ts","../src/cli/config.ts","../src/cli/graph-loader.ts","../src/cli/help.ts","../src/cli/watch.ts","../src/watch-ignore.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 type { ParallelParsingOption } from \"./graph/builder\";\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 /**\n * Explicit path-alias map for import resolution, same shape as tsconfig's\n * `compilerOptions.paths` (e.g. `{ \"@app/*\": [\"src/app/*\"] }`). Takes precedence over\n * any aliases declared in `tsconfig.json`. Useful for JS-only projects without a\n * tsconfig, or to mirror Vite/webpack alias configs. Substitution paths are resolved\n * relative to the project root.\n */\n pathAliases?: Record<string, 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 * Controls worker-pool offloading of file parsing (see docs/adr-010-parallel-parsing.md).\n * `true`/unset (default) enables it once a cheap pre-scan finds at least `minFiles`\n * (default 20) files; parsing a file is fast enough in most repos that the pool's\n * per-thread startup cost only pays off past roughly 600-700 files, so small/typical\n * repos may see slightly slower builds under the default — set `false` to always parse\n * in-process, or pass `{ minFiles, maxThreads }` to raise the threshold instead.\n */\n parallelParsing?: ParallelParsingOption;\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\n/**\n * @description Extracts the subset of `MokoshConfig` fields that affect graph\n * construction (`gitStats`, `parallelParsing`, `pathAliases`) into a plain options\n * object, ready to spread into `createImportMap`/`createWorkspaceGraph` calls. Single\n * source of truth for this mapping so every graph-building call site — CLI, MCP,\n * and secondary command-level rebuilds — stays in sync as new config fields are added.\n * @param config - The loaded config, or `undefined` when none has been loaded yet.\n * @returns Graph-build options with defaults applied (`gitStats` defaults to `false`).\n */\nexport function configToGraphOptions(config: MokoshConfig | undefined): {\n gitStats: boolean;\n parallelParsing: ParallelParsingOption | undefined;\n pathAliases: Record<string, string[]> | undefined;\n} {\n return {\n gitStats: config?.gitStats ?? false,\n parallelParsing: config?.parallelParsing,\n pathAliases: config?.pathAliases,\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 \".md\",\n \".mdx\",\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}\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, Go, and Python files\n * (see `CALL_EDGE_TYPES` in `../language-support`). Functions in other language\n * 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","/**\n * Cross-file duplicate-code detection. Two matching strategies, picked per file by `FileType`:\n * CSS/Less/SCSS route through `findStyleBlockDuplicates` (`style-blocks.ts`), a structural\n * comparator over PostCSS rule bodies; everything else (including Stylus, which has no shared\n * PostCSS AST here) runs the generic tokenize → shingle → chain-merge pipeline. See\n * docs/adr-012-duplicate-detection.md for why the shingle pipeline is built in-house instead of\n * adopting jscpd, and docs/adr-013-duplicate-detection-noise-reduction.md for why CSS-family\n * matching moved off it.\n *\n * `graph.nodes` is not a reliable ignore-rule-filtered file list: `DEFAULT_IGNORE_DIRS` and\n * extension filtering only gate `GraphBuilder`'s own FS-walk discovery passes, not files that\n * become reachable via a resolved reference (an import, or — for Markdown — a code-span file\n * reference per ADR-009). A doc that merely *mentions* `dist/parse-worker.js` in backticks can\n * pull that build artifact into the graph as a real node, ignore-dir or extension notwithstanding.\n * So this module applies its own filtering rather than trusting the graph's membership.\n */\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { DEFAULT_IGNORE_DIRS } from \"../../const\";\nimport { LOCK_FILE_NAMES } from \"../../parser/lockfile\";\nimport type { FileType } from \"../../types/parse\";\nimport type { Graph } from \"../model\";\nimport { type DuplicateFamily, getDuplicateFamily } from \"./families\";\nimport { type DuplicateGroup, type FileTokens, findDuplicateGroups } from \"./shingle\";\nimport { findStyleBlockDuplicates, type StyleSourceFile } from \"./style-blocks\";\nimport { tokenize } from \"./tokenizer\";\n\nexport type { DuplicateFamily } from \"./families\";\nexport type { DuplicateGroup, DuplicateOccurrence } from \"./shingle\";\nexport type { StyleSourceFile } from \"./style-blocks\";\n\n/** CSS-family types with a shared PostCSS AST available — routed through the structural rule-body\n * comparator instead of the generic token-shingle pipeline. Stylus has no such AST here, so it\n * stays on the token-shingle path (still isolated to the `\"style\"` family, see `families.ts`). */\nconst STRUCTURAL_STYLE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\"css\", \"scss\", \"less\"]);\n\nexport interface FindDuplicatesOptions {\n /** Minimum duplicated block size, in source lines, to report (default 6). */\n minLines?: number | undefined;\n /** Shingle window size, in tokens — the smallest duplicate the scan can detect (default 15). */\n windowSize?: number | undefined;\n /** When true (default), string/number literals are normalized too, so only structural shape\n * — not the specific values used — drives a match. Set false for stricter, Type-1-only matching. */\n ignoreLiterals?: boolean | undefined;\n /** Maximum fraction of a token-shingled block's window that may be object/array-literal\n * structural punctuation (`{ } : , [ ]`) (default 0.5) — gates out blocks that are mostly\n * schema/object-literal shape (e.g. MCP tool `inputSchema` boilerplate repeated across\n * unrelated tool definitions) rather than substantive shared logic. Does not apply to the\n * CSS/Less/SCSS structural comparator, which already matches on literal declaration content.\n * Set to 1 to disable. See docs/adr-013-duplicate-detection-noise-reduction.md. */\n maxPunctuationRatio?: number | undefined;\n /** Caps the number of duplicate blocks returned, largest-first (default 50). */\n limit?: number | undefined;\n /** Directory names to exclude, matched against any path segment (default `DEFAULT_IGNORE_DIRS`\n * — `node_modules`, `dist`, `.git`, `mokosh-cache`, `coverage`, etc.). Pass `[]` to disable. */\n ignoreDirs?: readonly string[] | undefined;\n}\n\n/**\n * @description Lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) contain large\n * blocks of genuinely repeated JSON/YAML structure — every dependency entry looks like every\n * other one — but that's not code duplication, and it swamps real findings by sheer line\n * count. Excluded regardless of `ignoreDirs`, since lock files sit at the project root, not in\n * an ignorable directory.\n * @param relPath - Project-relative file path.\n * @returns Whether `relPath`'s basename is a known lock file.\n */\nfunction isLockFile(relPath: string): boolean {\n return LOCK_FILE_NAMES.includes(path.basename(relPath));\n}\n\n/**\n * @description Whether any path segment of `relPath` matches an ignored directory name.\n * @param relPath - Project-relative file path.\n * @param ignoreDirs - Directory names to treat as excluded.\n * @returns `true` if `relPath` lives under one of `ignoreDirs`.\n */\nfunction isUnderIgnoredDir(relPath: string, ignoreDirs: readonly string[]): boolean {\n if (ignoreDirs.length === 0) return false;\n const ignored = new Set(ignoreDirs);\n return relPath.split(path.sep).some((segment) => ignored.has(segment));\n}\n\n/**\n * @description Scans every file already present in `graph` for cross-file (and within-file)\n * duplicated code. CSS/Less/SCSS files are compared structurally — by their rule bodies'\n * literal, ordered `property: value` declarations, independent of selector name — via\n * {@link findStyleBlockDuplicates}. Every other language (TS/JS, Python, Go, CoffeeScript,\n * LiveScript, Lua, Gherkin, Markdown, and Stylus, which has no shared PostCSS AST here) runs\n * the generic token-shingling pipeline instead: comments are stripped per `FileType`,\n * identifiers (and, by default, literals) are normalized to placeholders so renamed-variable\n * copies still match, then a sliding token window is hashed and chain-merged into contiguous\n * blocks. Token-shingled files are additionally partitioned into language families\n * ({@link getDuplicateFamily} — `\"style\"` for Stylus, `\"code\"` for everything else) so\n * matching never crosses that boundary — see docs/adr-013-duplicate-detection-noise-reduction.md.\n * @param graph - The graph to scan; its node paths (already ignore-rule-filtered) are the file\n * list, re-read from disk since duplication data isn't cached on `FileNode`.\n * @param rootDir - Absolute project root that graph paths are relative to.\n * @param options - `minLines`/`windowSize` tune token-shingle sensitivity (`minLines` also caps\n * CSS/Less/SCSS block size); `ignoreLiterals` toggles Type-2 vs Type-1 matching for the\n * token-shingle path only (CSS/Less/SCSS always match on literal declaration content);\n * `maxPunctuationRatio` gates out token-shingle blocks that are mostly object/array-literal\n * structural punctuation (e.g. schema/object-literal boilerplate) rather than substantive\n * shared logic; `ignoreDirs` excludes files under matching directory names; `limit` caps\n * results. Lock files are always excluded, independent of `ignoreDirs`.\n * @returns Duplicate blocks (each tagged with its `family`), two or more occurrences per block —\n * every block that pairwise chain-matches another is clustered into one group instead of one\n * per pair — sorted largest-first across all families.\n */\nexport async function findDuplicates(\n graph: Graph,\n rootDir: string,\n options: FindDuplicatesOptions = {},\n): Promise<DuplicateGroup[]> {\n const {\n minLines = 6,\n windowSize = 15,\n ignoreLiterals = true,\n maxPunctuationRatio = 0.5,\n limit = 50,\n ignoreDirs = DEFAULT_IGNORE_DIRS,\n } = options;\n\n const nodes = [...graph.nodes.values()].filter(\n (node) => !isLockFile(node.path) && !isUnderIgnoredDir(node.path, ignoreDirs),\n );\n const structuralStyleFiles: StyleSourceFile[] = [];\n const filesByFamily = new Map<DuplicateFamily, FileTokens[]>();\n await Promise.all(\n nodes.map(async (node) => {\n let source: string;\n try {\n source = await readFile(path.join(rootDir, node.path), \"utf8\");\n } catch {\n // File listed in the graph but no longer readable (deleted/moved since build) — skip.\n return;\n }\n\n if (STRUCTURAL_STYLE_TYPES.has(node.type)) {\n structuralStyleFiles.push({ file: node.path, source, fileType: node.type });\n return;\n }\n\n const family = getDuplicateFamily(node.type);\n const fileTokens: FileTokens = {\n file: node.path,\n tokens: tokenize(source, node.type, ignoreLiterals),\n };\n const bucket = filesByFamily.get(family);\n if (bucket) bucket.push(fileTokens);\n else filesByFamily.set(family, [fileTokens]);\n }),\n );\n\n const groups: DuplicateGroup[] = findStyleBlockDuplicates(structuralStyleFiles, minLines);\n\n // Token-shingle matching never crosses a family boundary (see families.ts) — each family's\n // shingle windows are hashed and chain-merged in isolation, so (e.g.) Stylus can never register\n // as a \"duplicate\" of an unrelated TS/JS/Python shape, and vice versa.\n for (const [family, fileTokens] of filesByFamily) {\n for (const group of findDuplicateGroups(\n fileTokens,\n windowSize,\n minLines,\n maxPunctuationRatio,\n )) {\n groups.push({ ...group, family });\n }\n }\n\n return groups.sort((a, b) => b.lines - a.lines).slice(0, limit);\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 { load as loadYaml } 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\n/**\n * Lock file basenames mokosh knows how to parse for version annotation. Exported so other\n * features (e.g. `findDuplicates`) can exclude these files by the same single source of truth\n * instead of re-declaring the list — a lock file's repeated JSON/YAML dependency blocks are\n * real textual repetition, but not code duplication.\n */\nexport const LOCK_FILE_NAMES: readonly string[] = [\n \"package-lock.json\",\n \"yarn.lock\",\n \"pnpm-lock.yaml\",\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 = loadYaml(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 = loadYaml(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 parsers: Record<string, (lockFilePath: string) => LockFileData> = {\n \"package-lock.json\": parsePackageLock,\n \"yarn.lock\": parseYarnLock,\n \"pnpm-lock.yaml\": parsePnpmLock,\n };\n const candidates = LOCK_FILE_NAMES.map(\n (filename) => [filename, parsers[filename] as (lockFilePath: string) => LockFileData] as const,\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","/**\n * Language-family partitioning for duplicate detection. `findDuplicates` never compares files\n * across a family boundary — see docs/adr-013-duplicate-detection-noise-reduction.md. The\n * immediate driver is CSS-family declarations: they share a small, finite vocabulary (property\n * names, common value keywords) that the shared `KEYWORDS` denylist in `tokenizer.ts` doesn't\n * cover, so unrelated style rules with the same declaration *shape* but different selectors/\n * properties/values were hashing identically to unrelated TS/JS/Python shapes too. Splitting by\n * family also lets later phases tune `windowSize`/`minLines`/vocabulary per family without one\n * family's tuning affecting another's.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One partition of `findDuplicates` matching — files in different families are never compared. */\nexport type DuplicateFamily = \"style\" | \"code\";\n\n/** CSS-family languages: small, finite declaration vocabulary compared to code identifiers. */\nconst STYLE_FILE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"css\",\n \"scss\",\n \"less\",\n \"stylus\",\n]);\n\n/**\n * @description Maps a `FileType` to the duplicate-detection family it belongs to. Everything\n * outside the CSS family (TS/JS, Python, Go, CoffeeScript, LiveScript, Lua, Gherkin, Markdown,\n * …) falls into the single `\"code\"` family — the shared `KEYWORDS` denylist in `tokenizer.ts`\n * is already oriented toward that group, so they can keep matching against each other as\n * `findDuplicates` does today.\n * @param fileType - The file's parsed language.\n * @returns The family `fileType` belongs to.\n */\nexport function getDuplicateFamily(fileType: FileType): DuplicateFamily {\n return STYLE_FILE_TYPES.has(fileType) ? \"style\" : \"code\";\n}\n","/**\n * Sliding-window (shingle) hashing over a normalized token stream, plus chain-merging of\n * consecutive matching windows into contiguous duplicate blocks, a structural-punctuation-density\n * gate that drops blocks that are mostly object/array-literal shape (e.g. MCP tool `inputSchema`\n * boilerplate) rather than substantive shared logic, and exact-occurrence clustering of pair-\n * matches into one N-occurrence group instead of reporting C(N,2) near-identical pairs for a block\n * repeated N times. Shared by every language `tokenize()` supports — the shingling step itself has\n * no language awareness at all. See docs/adr-013-duplicate-detection-noise-reduction.md for the\n * noise this addresses.\n */\nimport type { DuplicateFamily } from \"./families\";\nimport type { NormalizedToken } from \"./tokenizer\";\n\n/** One file's worth of tokens, kept together so a match can be traced back to its source. */\nexport interface FileTokens {\n file: string;\n tokens: NormalizedToken[];\n}\n\nexport interface DuplicateOccurrence {\n file: string;\n startLine: number;\n endLine: number;\n}\n\nexport interface DuplicateGroup {\n /** Every location sharing this duplicated block (two or more) — locations that pairwise\n * chain-match are clustered into one group instead of being reported once per pair, so a\n * block repeated N times produces one N-occurrence group, not C(N,2) near-identical ones. */\n occurrences: DuplicateOccurrence[];\n /** Line span of the largest single pairwise match clustered into this group (each pair's own\n * span is the shorter of its two occurrences) — the best-verified size for this block, not an\n * average or a value shrunk by a more weakly-matching cluster member. */\n lines: number;\n /** Token-window length backing this block, after chain-merging adjacent windows. */\n tokens: number;\n /** Which language family both occurrences belong to (set by `findDuplicates`, which never\n * matches across families — see docs/adr-013-duplicate-detection-noise-reduction.md).\n * Absent when called directly with a token stream that isn't family-scoped, e.g. in tests. */\n family?: DuplicateFamily | undefined;\n}\n\ninterface Location {\n file: string;\n windowIndex: number;\n}\n\n/**\n * @description A small non-cryptographic string hash (djb2), good enough to bucket identical\n * token windows together for a heuristic duplicate finder — collision risk is negligible at\n * the window sizes and file counts this operates on, and any collision only costs an extra\n * comparison, never a wrong report, since chains are still verified position-by-position.\n * @param text - The joined-token window text to hash.\n * @returns A hash string safe to use as a `Map` key.\n */\nfunction hashWindow(text: string): string {\n let hash = 5381;\n for (let i = 0; i < text.length; i++) {\n hash = (hash * 33 + text.charCodeAt(i)) >>> 0;\n }\n return hash.toString(36);\n}\n\n/**\n * @description Computes every `windowSize`-token window's hash for one file's token stream.\n * @param tokens - The file's normalized tokens.\n * @param windowSize - Number of tokens per shingle window.\n * @returns Hash per window index, aligned so `hashes[i]` is the window starting at token `i`.\n */\nfunction hashWindows(tokens: NormalizedToken[], windowSize: number): string[] {\n const hashes: string[] = [];\n for (let i = 0; i <= tokens.length - windowSize; i++) {\n hashes.push(\n hashWindow(\n tokens\n .slice(i, i + windowSize)\n .map((t) => t.text)\n .join(\" \"),\n ),\n );\n }\n return hashes;\n}\n\n/**\n * @description Extends a matching window pair forward for as long as the next window in both\n * files keeps hashing identically, turning one shingle match into the longest contiguous\n * duplicate block it's part of.\n * @param hashesByFile - Precomputed per-window hashes, one array per file.\n * @param a - Starting location in the first file.\n * @param b - Starting location in the second file.\n * @returns How many additional windows (beyond the first) also match.\n */\nfunction extendChain(hashesByFile: Map<string, string[]>, a: Location, b: Location): number {\n const hashesA = hashesByFile.get(a.file);\n const hashesB = hashesByFile.get(b.file);\n if (!hashesA || !hashesB) return 0;\n let extra = 0;\n while (\n a.windowIndex + extra + 1 < hashesA.length &&\n b.windowIndex + extra + 1 < hashesB.length &&\n hashesA[a.windowIndex + extra + 1] === hashesB[b.windowIndex + extra + 1]\n ) {\n extra++;\n }\n return extra;\n}\n\n/**\n * @description Whether `(a, b)` is the start of a duplicate chain rather than the middle of\n * one already found from an earlier starting position — true when the previous window pair\n * did *not* also match, so each contiguous run is reported exactly once, from its earliest\n * position.\n */\nfunction isChainStart(hashesByFile: Map<string, string[]>, a: Location, b: Location): boolean {\n if (a.windowIndex === 0 || b.windowIndex === 0) return true;\n const hashesA = hashesByFile.get(a.file);\n const hashesB = hashesByFile.get(b.file);\n if (!hashesA || !hashesB) return true;\n return hashesA[a.windowIndex - 1] !== hashesB[b.windowIndex - 1];\n}\n\n/** Plain union-find over opaque string keys, path-compressed. Used below to cluster pair-matches\n * that share an *exact* occurrence — same file and identical start/end line — into one group,\n * rather than one report per pair. Deliberately not transitive over \"chain-matches some other\n * location that chain-matches a third\" in general: two different (file, start) locations can\n * each independently chain-match a shared window to very different lengths (e.g. one long\n * genuine clone plus, separately, several short internally-repeated sub-patterns within it), and\n * merging on that looser basis would force every member down to the shortest edge's length,\n * silently discarding the strongest match. Requiring an exact shared span sidesteps that: it\n * only fires when two independently-computed pair-matches agree on the very same block for one\n * of their two sides (the same repeated-boilerplate location matched against several others),\n * which is exactly the pair-explosion case this clustering targets. */\nclass ExactOccurrenceUnionFind {\n private readonly parent = new Map<string, string>();\n\n private find(key: string): string {\n if (!this.parent.has(key)) this.parent.set(key, key);\n let root = key;\n while (this.parent.get(root) !== root) root = this.parent.get(root) as string;\n let cur = key;\n while (cur !== root) {\n const next = this.parent.get(cur) as string;\n this.parent.set(cur, root);\n cur = next;\n }\n return root;\n }\n\n union(keyA: string, keyB: string): void {\n const rootA = this.find(keyA);\n const rootB = this.find(keyB);\n if (rootA !== rootB) this.parent.set(rootA, rootB);\n }\n\n root(key: string): string {\n return this.find(key);\n }\n}\n\n/** `\"<file>#<startLine>#<endLine>\"` — exact-span identity for one reported occurrence. */\nfunction occurrenceKey(occ: DuplicateOccurrence): string {\n return `${occ.file}#${occ.startLine}#${occ.endLine}`;\n}\n\n/** Object/array-literal structural punctuation — braces, colons, commas, brackets. Deliberately\n * narrower than \"all punctuation\": parens and semicolons are just as common in ordinary control\n * flow and would blur the signal below. */\nconst STRUCTURAL_PUNCTUATION = new Set([\"{\", \"}\", \":\", \",\", \"[\", \"]\"]);\n\n/**\n * @description The fraction of a token window's texts that are object/array-literal structural\n * punctuation (`{ } : , [ ]`), used to gate out blocks that are mostly object-literal/schema\n * shape — e.g. MCP tool `inputSchema` boilerplate repeated across unrelated tool definitions —\n * rather than substantive shared logic. An earlier version of this gate measured *identifier*\n * diversity instead (rejecting windows where too few distinct token texts appear), but that\n * measure doesn't distinguish schema boilerplate from genuine duplication: table-driven test\n * fixtures (deliberately mirrored across, e.g., parallel Go/Python test suites) are just as\n * token-repetitive as schema boilerplate, so it silently discarded real matches. Punctuation\n * density instead targets the *shape* that's actually distinctive about object/schema literals\n * — dense braces/colons/commas relative to keywords and content — which genuine control-flow\n * logic and repeated test-fixture data don't share, verified empirically against mokosh's own\n * codebase (~0.60–0.74 for schema boilerplate vs. ~0.07–0.43 for real duplicated logic,\n * including table-driven test fixtures). See docs/adr-013-duplicate-detection-noise-reduction.md.\n * @param tokens - The full token stream a window is sliced from.\n * @param start - Window start index.\n * @param length - Window length in tokens.\n * @returns `structuralPunctuationTokens / length`, in `[0, 1]`.\n */\nfunction structuralPunctuationRatio(\n tokens: NormalizedToken[],\n start: number,\n length: number,\n): number {\n let count = 0;\n for (let i = start; i < start + length; i++) {\n if (STRUCTURAL_PUNCTUATION.has((tokens[i] as NormalizedToken).text)) count++;\n }\n return count / length;\n}\n\ninterface PairMatch {\n occA: DuplicateOccurrence;\n occB: DuplicateOccurrence;\n lines: number;\n tokens: number;\n}\n\n/**\n * @description Builds duplicate groups from a set of files' token streams: hashes every\n * `windowSize`-token sliding window per file, pairs up locations sharing a hash, chain-merges\n * each chain-starting pair forward into the longest contiguous block it starts (exactly as a\n * pairwise-only detector would), drops pairs whose block is mostly object/array-literal\n * structural punctuation rather than substantive content, then merges pair-matches that share\n * an exact occurrence (same file, same start/end line on one side) into a single group — so a\n * block repeated N times across the project is reported once, with N occurrences, instead of\n * once per pair, without shortening any individual match to accommodate an unrelated one.\n * @param files - Per-file normalized token streams (already comment-stripped and identifier/\n * literal-normalized by `tokenize`).\n * @param windowSize - Shingle window length in tokens — the smallest duplicate this can find.\n * @param minLines - Minimum merged block size (in source lines) to report; smaller matches\n * are almost always incidental (short getters, boilerplate imports) rather than real\n * duplication.\n * @param maxPunctuationRatio - Maximum fraction of a block's window that may be object/array-\n * literal structural punctuation (default 0.5) — see {@link structuralPunctuationRatio}. Set\n * to 1 to disable the gate.\n * @returns Duplicate blocks, each with two or more occurrences, sorted largest-first.\n */\nexport function findDuplicateGroups(\n files: FileTokens[],\n windowSize: number,\n minLines: number,\n maxPunctuationRatio = 0.5,\n): DuplicateGroup[] {\n const hashesByFile = new Map<string, string[]>();\n const tokensByFile = new Map<string, NormalizedToken[]>();\n const buckets = new Map<string, Location[]>();\n\n for (const { file, tokens } of files) {\n if (tokens.length < windowSize) continue;\n tokensByFile.set(file, tokens);\n const hashes = hashWindows(tokens, windowSize);\n hashesByFile.set(file, hashes);\n for (let i = 0; i < hashes.length; i++) {\n const loc: Location = { file, windowIndex: i };\n const bucket = buckets.get(hashes[i] as string);\n if (bucket) bucket.push(loc);\n else buckets.set(hashes[i] as string, [loc]);\n }\n }\n\n const pairMatches: PairMatch[] = [];\n\n for (const locations of buckets.values()) {\n if (locations.length < 2) continue;\n for (let i = 0; i < locations.length; i++) {\n for (let j = i + 1; j < locations.length; j++) {\n const a = locations[i] as Location;\n const b = locations[j] as Location;\n if (a.file === b.file && a.windowIndex === b.windowIndex) continue;\n if (!isChainStart(hashesByFile, a, b)) continue;\n\n const extra = extendChain(hashesByFile, a, b);\n const length = windowSize + extra;\n\n const tokensA = tokensByFile.get(a.file) as NormalizedToken[];\n if (structuralPunctuationRatio(tokensA, a.windowIndex, length) > maxPunctuationRatio)\n continue;\n\n // Same-file self-overlap isn't a real duplicate (it's just the same span vs itself).\n if (a.file === b.file) {\n const [lo, hi] = a.windowIndex < b.windowIndex ? [a, b] : [b, a];\n if (lo.windowIndex + length > hi.windowIndex) continue;\n }\n\n const tokensB = tokensByFile.get(b.file) as NormalizedToken[];\n const occA: DuplicateOccurrence = {\n file: a.file,\n startLine: (tokensA[a.windowIndex] as NormalizedToken).line,\n endLine: (tokensA[a.windowIndex + length - 1] as NormalizedToken).line,\n };\n const occB: DuplicateOccurrence = {\n file: b.file,\n startLine: (tokensB[b.windowIndex] as NormalizedToken).line,\n endLine: (tokensB[b.windowIndex + length - 1] as NormalizedToken).line,\n };\n const lines = Math.min(\n occA.endLine - occA.startLine + 1,\n occB.endLine - occB.startLine + 1,\n );\n if (lines < minLines) continue;\n\n pairMatches.push({ occA, occB, lines, tokens: length });\n }\n }\n }\n\n const clusters = new ExactOccurrenceUnionFind();\n const occurrenceByKey = new Map<string, DuplicateOccurrence>();\n for (const { occA, occB } of pairMatches) {\n const keyA = occurrenceKey(occA);\n const keyB = occurrenceKey(occB);\n occurrenceByKey.set(keyA, occA);\n occurrenceByKey.set(keyB, occB);\n clusters.union(keyA, keyB);\n }\n\n const keysByRoot = new Map<string, Set<string>>();\n for (const key of occurrenceByKey.keys()) {\n const root = clusters.root(key);\n const keys = keysByRoot.get(root);\n if (keys) keys.add(key);\n else keysByRoot.set(root, new Set([key]));\n }\n\n const groups: DuplicateGroup[] = [];\n for (const keys of keysByRoot.values()) {\n if (keys.size < 2) continue;\n const occurrences = [...keys].map((key) => occurrenceByKey.get(key) as DuplicateOccurrence);\n // Best (largest) individual pairwise match fully contained in this cluster — reported as the\n // group's size, rather than a value shrunk by an unrelated, more weakly-matching member.\n let lines = 0;\n let tokens = windowSize;\n for (const pm of pairMatches) {\n if (!keys.has(occurrenceKey(pm.occA)) || !keys.has(occurrenceKey(pm.occB))) continue;\n if (pm.lines > lines) {\n lines = pm.lines;\n tokens = pm.tokens;\n }\n }\n groups.push({ occurrences, lines, tokens });\n }\n\n return groups.sort((a, b) => b.lines - a.lines);\n}\n","/** Parses CSS and Less files using PostCSS to extract @import edges, plus root-level Less variable exports. */\nimport postcss from \"postcss\";\nimport * as less from \"postcss-less\";\nimport type { ExportedSymbol, ImportEdge, StructuredTag } from \"../../types/node\";\n\nconst lessParser = 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 Walks the root-level nodes of a parsed Less AST and collects `@variable: value;`\n * declarations as the file's exported surface. `postcss-less` represents these as `atrule` nodes\n * carrying a non-standard `value` property (unlike real at-rules such as `@media`/`@import`, which\n * never have one) — that's the reliable signal used to tell a variable apart from a directive.\n * Mixin *definitions* (`.name(...) { }`) are deliberately not extracted — distinguishing a definition\n * from an ordinary rule with a parenthesized selector, or from a mixin *call* (`.name();`), needs\n * deeper Less semantics than a straight AST walk gives for free.\n * @param {postcss.Root} root - The parsed Less AST\n * @returns {{ exports: ExportedSymbol[]; tags: StructuredTag[] }} Root-level Less variable exports and their matching declaration tags\n */\nfunction extractLessVariableExports(root: postcss.Root): {\n exports: ExportedSymbol[];\n tags: StructuredTag[];\n} {\n const exports: ExportedSymbol[] = [];\n const tags: StructuredTag[] = [];\n\n for (const node of root.nodes ?? []) {\n if (node.type !== \"atrule\") continue;\n const { value } = node as unknown as { value?: string };\n if (value === undefined) continue;\n const name = node.name;\n if (!name) continue;\n exports.push({ name });\n tags.push({ name, kind: \"variable\" });\n }\n\n return { exports, tags };\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 * Also extracts root-level `@variable` declarations as the file's exports and matching declaration tags.\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; exports: ExportedSymbol[]; tags: StructuredTag[] }} The collected import edges, the PostCSS root (may be empty on parse failure), and the file's exported surface\n */\nexport function parseLessContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root; exports: ExportedSymbol[]; tags: StructuredTag[] } {\n try {\n const root = lessParser.parse(content);\n const { exports, tags } = extractLessVariableExports(root);\n return { imports: collectEdgesFromRoot(root, filePath), root, exports, tags };\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 {\n imports: regexFallbackImports(content, filePath),\n root: postcss.parse(\"\"),\n exports: [],\n tags: [],\n };\n }\n}\n","/** Parses SCSS/Sass files using postcss-scss to extract @use, @forward, and @import edges, plus root-level variable/mixin/function exports. */\nimport type postcss from \"postcss\";\nimport { parse as scssParse } from \"postcss-scss\";\nimport type { ExportedSymbol, ImportEdge, StructuredTag } from \"../../types/node\";\n\n/**\n * @description Returns true when a Sass identifier follows the module-privacy convention\n * (leading `_` or `-`), meaning it is never visible outside the file via `@use` and so is\n * not part of the module's exported surface.\n * @param {string} name - The variable, mixin, or function name (without its `$` sigil, if any)\n * @returns {boolean} `true` for private names\n */\nfunction isScssPrivateName(name: string): boolean {\n return name.startsWith(\"_\") || name.startsWith(\"-\");\n}\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 Walks the root-level nodes of a parsed SCSS AST and collects `$variable` declarations\n * and `@mixin`/`@function` at-rules as the file's exported surface, mirroring how `export` works in TS.\n * Skips Sass-private names (leading `_`/`-`), which are never visible outside the file via `@use`,\n * and skips anything not declared directly at the root (rule- or mixin-body-scoped declarations are local).\n * @param {postcss.Root} root - The parsed SCSS AST\n * @returns {{ exports: ExportedSymbol[]; tags: StructuredTag[] }} Root-level variable/mixin/function exports and their matching declaration tags\n */\nfunction extractScssExports(root: postcss.Root): {\n exports: ExportedSymbol[];\n tags: StructuredTag[];\n} {\n const exports: ExportedSymbol[] = [];\n const tags: StructuredTag[] = [];\n\n for (const node of root.nodes ?? []) {\n if (node.type === \"decl\" && node.prop.startsWith(\"$\")) {\n const name = node.prop.slice(1);\n if (!name || isScssPrivateName(name)) continue;\n exports.push({ name });\n tags.push({ name, kind: \"variable\" });\n continue;\n }\n\n if (node.type === \"atrule\" && (node.name === \"mixin\" || node.name === \"function\")) {\n const match = node.params.match(/^([\\w-]+)/);\n const name = match?.[1];\n if (!name || isScssPrivateName(name)) continue;\n const signature = node.params.trim();\n exports.push(signature.includes(\"(\") ? { name, signature } : { name });\n tags.push({ name, kind: \"function\" });\n }\n }\n\n return { exports, tags };\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 * Also extracts root-level `$variable`/`@mixin`/`@function` declarations as the file's exports and matching declaration tags.\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; exports: ExportedSymbol[]; tags: StructuredTag[] }} The collected import edges, the PostCSS root (used for barrel detection), and the file's exported surface\n */\nexport function parseScssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root; exports: ExportedSymbol[]; tags: StructuredTag[] } {\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 const { exports, tags } = extractScssExports(root);\n return { imports, root, exports, tags };\n}\n","/**\n * Structural (not token-shingled) duplicate detection for CSS-family \"block definitions\" — a\n * rule's declaration *body* (its literal, ordered `property: value` pairs), independent of the\n * rule's own selector. Reuses the PostCSS ASTs the CSS/Less/SCSS parsers already build\n * (`src/parser/style/*`) instead of a generic token window, so matching happens on the actual\n * declaration content rather than a normalized token shape — a `display: flex` rule and a\n * `display: block` rule can never register as a duplicate just because both collapse to the same\n * `ID : ID ;` shape, which was the false-positive mode the token-shingle path had for CSS. See\n * docs/adr-013-duplicate-detection-noise-reduction.md.\n *\n * Stylus is deliberately excluded: it has no PostCSS-compatible AST available in this codebase\n * (`src/parser/style/stylus.ts` only regex-extracts imports), so Stylus files still fall back to\n * the generic token-shingle pipeline in `tokenizer.ts`/`shingle.ts`.\n */\nimport type postcss from \"postcss\";\nimport { parseCssContent, parseLessContent } from \"../../parser/style/css\";\nimport { parseScssContent } from \"../../parser/style/scss\";\nimport type { FileType } from \"../../types/parse\";\nimport type { DuplicateGroup, DuplicateOccurrence } from \"./shingle\";\n\nexport interface StyleSourceFile {\n file: string;\n source: string;\n fileType: FileType;\n}\n\ninterface RuleBlock {\n file: string;\n startLine: number;\n endLine: number;\n lines: number;\n signature: string;\n declCount: number;\n}\n\n/**\n * @description Collapses a declaration value to a comparable form without touching its literal\n * content — only incidental whitespace differences (copy-pasted code reformatted by a linter,\n * say) are normalized away.\n * @param value - A declaration's raw PostCSS `value`.\n * @returns The value trimmed and with internal whitespace runs collapsed to one space.\n */\nfunction normalizeValue(value: string): string {\n return value.trim().replace(/\\s+/g, \" \");\n}\n\n/**\n * @description Parses one style file into a PostCSS AST using the same dialect-specific parser\n * `src/parser/style/index.ts` dispatches to, so block extraction sees exactly what the graph\n * builder saw. Returns `undefined` for Stylus (no shared AST) or on a parse failure.\n * @param file - The file to parse; `fileType` selects the CSS/Less/SCSS dialect.\n * @returns The parsed PostCSS root, or `undefined` when structural parsing isn't available.\n */\nfunction rootFor(file: StyleSourceFile): postcss.Root | undefined {\n try {\n if (file.fileType === \"scss\") return parseScssContent(file.source, file.file).root;\n if (file.fileType === \"less\") return parseLessContent(file.source, file.file).root;\n if (file.fileType === \"css\") return parseCssContent(file.source, file.file).root;\n return undefined;\n } catch {\n // Malformed input the dialect parser couldn't recover from — skip rather than throw, same\n // trade-off the token-shingle path makes for unreadable files.\n return undefined;\n }\n}\n\n/**\n * @description Walks a style file's AST and extracts every rule's declaration body as a\n * comparable block, regardless of nesting depth (`@media`-wrapped and SCSS-nested rules are\n * walked the same as top-level ones).\n * @param file - The file to extract rule blocks from.\n * @returns One `RuleBlock` per rule that has at least one declaration.\n */\nfunction collectRuleBlocks(file: StyleSourceFile): RuleBlock[] {\n const root = rootFor(file);\n if (!root) return [];\n const blocks: RuleBlock[] = [];\n\n root.walkRules((rule) => {\n const decls = rule.nodes?.filter((node) => node.type === \"decl\") ?? [];\n if (decls.length === 0) return;\n\n const firstDecl = decls[0];\n const lastDecl = decls[decls.length - 1];\n const startLine = rule.source?.start?.line ?? firstDecl?.source?.start?.line;\n const endLine = lastDecl?.source?.end?.line ?? lastDecl?.source?.start?.line ?? startLine;\n if (startLine === undefined || endLine === undefined) return;\n\n const signature = decls\n .map((decl) => `${decl.prop.trim().toLowerCase()}:${normalizeValue(decl.value)}`)\n .join(\";\");\n\n blocks.push({\n file: file.file,\n startLine,\n endLine,\n lines: endLine - startLine + 1,\n signature,\n declCount: decls.length,\n });\n });\n\n return blocks;\n}\n\n/**\n * @description Finds duplicated CSS/Less/SCSS rule *bodies* — the literal, ordered set of\n * `property: value` declarations a rule contains — independent of the rule's own selector.\n * Two rules with different selectors but an identical declaration list are reported (a real\n * \"should this become a shared class or mixin\" signal); two rules that merely share\n * declaration *shape* but differ in property or value never are, unlike generic token-shingle\n * matching.\n * @param files - Style-family source files to scan. Stylus entries are silently skipped —\n * callers should route them through the token-shingle path instead.\n * @param minLines - Minimum line span a duplicated block must cover to be reported.\n * @param minDeclarations - Minimum number of declarations a duplicated block must contain, so a\n * single shared one-liner (e.g. `display: none;` alone) doesn't count as duplication.\n * @returns Duplicate blocks, each a pair of occurrences, sorted largest-first. Every group is\n * tagged `family: \"style\"`.\n */\nexport function findStyleBlockDuplicates(\n files: StyleSourceFile[],\n minLines: number,\n minDeclarations = 2,\n): DuplicateGroup[] {\n const buckets = new Map<string, RuleBlock[]>();\n for (const file of files) {\n for (const block of collectRuleBlocks(file)) {\n if (block.declCount < minDeclarations || block.lines < minLines) continue;\n const bucket = buckets.get(block.signature);\n if (bucket) bucket.push(block);\n else buckets.set(block.signature, [block]);\n }\n }\n\n const groups: DuplicateGroup[] = [];\n for (const bucket of buckets.values()) {\n for (let i = 0; i < bucket.length; i++) {\n for (let j = i + 1; j < bucket.length; j++) {\n const a = bucket[i] as RuleBlock;\n const b = bucket[j] as RuleBlock;\n // Same-file self-match on the exact same span isn't a real duplicate.\n if (a.file === b.file && a.startLine === b.startLine && a.endLine === b.endLine) continue;\n\n const occA: DuplicateOccurrence = {\n file: a.file,\n startLine: a.startLine,\n endLine: a.endLine,\n };\n const occB: DuplicateOccurrence = {\n file: b.file,\n startLine: b.startLine,\n endLine: b.endLine,\n };\n groups.push({\n occurrences: [occA, occB],\n lines: Math.min(a.lines, b.lines),\n tokens: a.declCount,\n family: \"style\",\n });\n }\n }\n }\n\n return groups.sort((a, b) => b.lines - a.lines);\n}\n","/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, CoffeeScript,\n * LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: identifiers always collapse to a single placeholder (so renamed-variable clones\n * still hash identically), and literals optionally collapse too (`ignoreLiterals`, default\n * on) so only structural shape — not the specific values used — drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment-stripping rule; unrecognised types skip stripping.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripComments(source, COMMENT_SYNTAX[fileType]);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\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","/** Single source of truth for which languages' parsers track which precision-relevant data. */\nimport type { FileType } from \"../types/parse\";\nimport type { Graph } from \"./model\";\n\n/** File types whose parser ever populates `FileNode.exports`. */\nexport const EXPORT_TRACKING_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"python\",\n \"go\",\n \"scss\",\n \"less\",\n \"lua\",\n \"coffeescript\",\n]);\n\n/** File types whose parser records which named symbols each import edge pulls in (`ImportEdge.symbols`). */\nexport const IMPORT_SYMBOL_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"python\",\n]);\n\n/** File types whose parser records function-level call edges (`FileNode.callEdges`). */\nexport const CALL_EDGE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"go\",\n \"python\",\n]);\n\nexport interface LanguageCoverage {\n type: FileType;\n fileCount: number;\n exportsTracked: boolean;\n importSymbolsTracked: boolean;\n callEdgesTracked: boolean;\n}\n\n/**\n * @description Reports which precision-relevant data mokosh actually tracks for each language\n * present in `graph` — so a caller can tell upfront whether tools like `find_symbol` or\n * `get_call_graph` will give call-level precision, degrade to import-level, or find nothing\n * at all for a given file, before running a query and being surprised by the result.\n * @param graph - The graph to summarize.\n * @returns One entry per `FileType` actually present in `graph`, sorted by file count\n * descending. Languages with zero files in this graph are omitted.\n */\nexport function getLanguageCoverage(graph: Graph): LanguageCoverage[] {\n const counts = new Map<FileType, number>();\n for (const node of graph.nodes.values()) {\n counts.set(node.type, (counts.get(node.type) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .map(([type, fileCount]) => ({\n type,\n fileCount,\n exportsTracked: EXPORT_TRACKING_TYPES.has(type),\n importSymbolsTracked: IMPORT_SYMBOL_TYPES.has(type),\n callEdgesTracked: CALL_EDGE_TYPES.has(type),\n }))\n .sort((a, b) => b.fileCount - a.fileCount);\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","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","/** Pure graph query/shaping functions shared by the MCP handlers and the CLI, so both surfaces return identical JSON shapes. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport type { FileNode } from \"../types/node\";\nimport type { Graph } from \"./model\";\nimport { SymbolTraversalContext } from \"./symbol-traversal\";\nimport type { WorkspaceGraph } from \"./workspace-model\";\n\nexport interface PathWithSymbols {\n path: string;\n symbols?: string[];\n}\n\nexport interface NodeMeta {\n category: FileNode[\"category\"];\n exports: string[];\n}\n\n/**\n * @description Looks up a node's category and export names, for augmenting traversal\n * results with `withMeta` so the AI doesn't need a follow-up `query` call.\n * @param graph - The graph to look up in.\n * @param path - Project-relative path of the node.\n * @returns `{ category, exports }` for the node, or undefined if `path` isn't in the graph.\n */\nexport function getNodeMeta(graph: Graph, path: string): NodeMeta | undefined {\n const node = graph.nodes.get(path);\n if (!node) return undefined;\n return { category: node.category, exports: node.exports.map((exportedSym) => exportedSym.name) };\n}\n\n/**\n * @description Outgoing traversal from `file` — all files it imports, up to `depth` hops.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the starting node.\n * @param depth - Max traversal depth (default 1 = immediate imports only).\n * @returns Reachable imported paths, each with the symbols imported from it when known.\n */\nexport function getDependencies(graph: Graph, file: string, depth = 1): PathWithSymbols[] {\n const deps: PathWithSymbols[] = [];\n graph.traverse(\n file,\n (node, _depth, parentPath) => {\n if (node.path === file) return true;\n const edge = parentPath\n ? graph.nodes.get(parentPath)?.imports.find((importEdge) => importEdge.toPath === node.path)\n : undefined;\n deps.push({ path: node.path, ...(edge?.symbols ? { symbols: edge.symbols } : {}) });\n return true;\n },\n { direction: \"outgoing\", maxDepth: depth },\n );\n return deps;\n}\n\n/**\n * @description Incoming one-hop traversal — files that directly import `file`.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the target node.\n * @returns Direct importers, each with the symbols they import from `file` when known.\n */\nexport function getDependents(graph: Graph, file: string): PathWithSymbols[] {\n const dependents: PathWithSymbols[] = [];\n graph.traverse(\n file,\n (node) => {\n if (node.path === file) return true;\n const edge = node.imports.find((importEdge) => importEdge.toPath === file);\n dependents.push({ path: node.path, ...(edge?.symbols ? { symbols: edge.symbols } : {}) });\n return true;\n },\n { direction: \"incoming\", maxDepth: 1 },\n );\n return dependents;\n}\n\nexport interface GetAffectedOptions {\n testsOnly?: boolean | undefined;\n changedSymbols?: string[] | undefined;\n}\n\n/**\n * @description Full incoming traversal from `file` upward — every file transitively affected if `file` changes.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the changed node.\n * @param options - `testsOnly` restricts results to test/spec files; `changedSymbols` restricts propagation to files that actually import those symbols.\n * @returns Project-relative paths of all transitively impacted files.\n */\nexport function getAffected(\n graph: Graph,\n file: string,\n options: GetAffectedOptions = {},\n): string[] {\n const { testsOnly = false, changedSymbols } = options;\n const ctx = changedSymbols ? new SymbolTraversalContext(file, changedSymbols) : null;\n const affected: string[] = [];\n graph.traverse(\n file,\n (node, _depth, parentPath) => {\n if (node.path === file) return true;\n if (ctx && parentPath && !ctx.updateAffectedSymbols(node, parentPath)) return false;\n const isTest = node.category === \"test\" || node.tags.some((tag) => tag.name === \"test\");\n if (!testsOnly || isTest) affected.push(node.path);\n return true;\n },\n { direction: \"incoming\" },\n );\n return affected;\n}\n\nexport interface CallerEntry {\n file: string;\n edges?: Array<{ from: string; to: string }>;\n}\n\nexport interface GetCallersOptions {\n depth?: number | undefined;\n withEdgeDetail?: boolean | undefined;\n}\n\n/**\n * @description Incoming call-edge traversal — files whose exported functions call into `file`.\n * More precise than `getAffected` because it follows runtime call edges rather than all import edges.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the target node.\n * @param options - `depth` caps traversal hops (default 1); `withEdgeDetail` adds from/to function names per edge.\n * @returns Callers, each optionally including edge detail.\n */\nexport function getCallers(\n graph: Graph,\n file: string,\n options: GetCallersOptions = {},\n): CallerEntry[] {\n const { depth = 1, withEdgeDetail = false } = options;\n const callers: CallerEntry[] = [];\n graph.traverseCalls(\n file,\n (node) => {\n if (node.path === file) return true;\n const entry: CallerEntry = { file: node.path };\n if (withEdgeDetail) {\n entry.edges = (node.callEdges ?? [])\n .filter((callEdge) => callEdge.toFile === file)\n .map((callEdge) => ({ from: callEdge.from, to: callEdge.to }));\n }\n callers.push(entry);\n return true;\n },\n { direction: \"incoming\", maxDepth: depth },\n );\n return callers;\n}\n\nexport interface ComplexFunctionEntry {\n file: string;\n name: string;\n line: number;\n complexity: number;\n cognitiveComplexity: number;\n}\n\nexport interface FindComplexFunctionsOptions {\n metric?: \"cognitiveComplexity\" | \"complexity\" | undefined;\n threshold?: number | undefined;\n limit?: number | undefined;\n}\n\n/**\n * @description Scans every file's per-function complexity breakdown and returns functions/methods\n * at or above the given threshold, sorted worst-first. Populated for TypeScript/JavaScript, Go,\n * and Python.\n * @param graph - The graph to scan.\n * @param options - `metric` picks which score to threshold/sort on (default `cognitiveComplexity`); `threshold` is the minimum score to include (default 10); `limit` caps the results (default 20).\n * @returns Matching functions, sorted worst-first.\n */\nexport function findComplexFunctions(\n graph: Graph,\n options: FindComplexFunctionsOptions = {},\n): ComplexFunctionEntry[] {\n const { metric = \"cognitiveComplexity\", threshold = 10, limit = 20 } = options;\n return [...graph.nodes.values()]\n .flatMap((node) =>\n (node.functions ?? [])\n .filter((fn) => fn[metric] >= threshold)\n .map((fn) => ({\n file: node.path,\n name: fn.name,\n line: fn.line,\n complexity: fn.complexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n })),\n )\n .sort((a, b) => b[metric] - a[metric])\n .slice(0, limit);\n}\n\nexport interface SlimNode {\n path: string;\n type: FileNode[\"type\"];\n category: FileNode[\"category\"];\n exports: string[];\n tags: string[];\n importsFiles: string[];\n description?: string;\n testedBy?: string[];\n coveragePct?: number;\n avgExportUsage?: number;\n maxExportUsage?: number;\n}\n\nexport interface SlimSerializedGraph {\n nodes: SlimNode[];\n cycles: string[][] | undefined;\n}\n\n/**\n * @description Strips a serialized graph down to a compact response: export names, meaningful tags,\n * and a flat importsFiles path list — no edge objects, no mtime/size.\n * @param filtered - A `SerializedGraph` (typically the output of `filterGraph`) to compact.\n * @returns The slim node list plus cycle info.\n */\nexport function slimSerialize(filtered: SerializedGraph): SlimSerializedGraph {\n const slimNodes = filtered.nodes.map((node) => ({\n path: node.path,\n type: node.type,\n category: node.category,\n exports: node.exports.map((exportedSym) => exportedSym.name),\n tags: node.tags\n .filter((tag) => tag.kind === \"comment-marker\" || tag.kind === \"import\")\n .map((tag) => tag.name),\n importsFiles: node.imports\n .filter((imp) => !imp.isExternal && imp.toPath)\n .map((imp) => imp.toPath as string),\n ...(node.description !== undefined && { description: node.description }),\n ...(node.testedBy !== undefined && { testedBy: node.testedBy }),\n ...(node.coveragePct !== undefined && { coveragePct: node.coveragePct }),\n ...(node.avgExportUsage !== undefined && { avgExportUsage: node.avgExportUsage }),\n ...(node.maxExportUsage !== undefined && { maxExportUsage: node.maxExportUsage }),\n }));\n return { nodes: slimNodes, cycles: filtered.cycles };\n}\n\nexport interface WorkspacePackageSummary {\n name: string;\n relativeRoot: string;\n nodeCount: number;\n dependsOn: string[];\n}\n\nexport interface WorkspacePackagesSummary {\n monorepoType: string;\n packageCount: number;\n packages: WorkspacePackageSummary[];\n}\n\n/**\n * @description Summarizes every package in a workspace graph: node counts and cross-package dependencies.\n * @param wg - The workspace graph to summarize.\n * @returns Monorepo type, package count, and per-package details.\n */\nexport function summarizeWorkspacePackages(wg: WorkspaceGraph): WorkspacePackagesSummary {\n const pkgDeps = wg.getPackageDependencies();\n const packages = Array.from(wg.packages.values()).map(({ graph, pkg }) => ({\n name: pkg.name,\n relativeRoot: pkg.relativeRoot,\n nodeCount: graph.nodes.size,\n dependsOn: pkgDeps.get(pkg.name) ?? [],\n }));\n return { monorepoType: wg.type, packageCount: packages.length, packages };\n}\n\n/**\n * @description Returns `true` if at least one node in the graph has coverage data loaded.\n * @param graph - The graph to check.\n * @returns Whether any node has a defined `coveragePct`.\n */\nexport function hasCoverageData(graph: Graph): boolean {\n return [...graph.nodes.values()].some((node) => node.coveragePct !== undefined);\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","/** Symbol-name lookup across the whole graph — generalizes queryCallGraph beyond TS/JS functions. */\nimport type { NodeCategory } from \"../types/parse\";\nimport { CALL_EDGE_TYPES } from \"./language-support\";\nimport type { Graph } from \"./model\";\nimport { getDependents } from \"./queries\";\n\nexport type SymbolPrecision = \"call\" | \"file-level\";\n\nexport interface SymbolCaller {\n file: string;\n callerFunction: string;\n}\n\nexport interface SymbolImporter {\n file: string;\n symbols?: string[];\n}\n\nexport interface SymbolMatch {\n path: string;\n category: NodeCategory;\n precision: SymbolPrecision;\n callers?: SymbolCaller[];\n importers?: SymbolImporter[];\n}\n\n/**\n * @description Finds every file that exports a symbol by name, with the best available\n * usage info per match. Precision depends on what the defining file's language parser\n * tracks: TS/JS, Go, and Python get function-level callers via call edges (`\"call\"`);\n * everything else falls back to whole-file dependents (`\"file-level\"` — import-level, not\n * symbol-level, since the file might not even use this specific export). Coverage still\n * differs across the `\"call\"` languages: TS/JS tracks any directly imported symbol, Go only\n * package-qualified calls, Python only bare calls to `from <module> import <name>` symbols.\n * @param graph - The graph to search.\n * @param name - Exact export name to look up.\n * @returns One entry per file that exports `name`; empty if no file does (including\n * languages — CoffeeScript, LiveScript, Lua, Gherkin, Markdown, CSS/SCSS/Stylus — whose\n * parsers never populate `exports` at all).\n */\nexport function findSymbol(graph: Graph, name: string): SymbolMatch[] {\n const matches: SymbolMatch[] = [];\n\n for (const node of graph.nodes.values()) {\n if (!node.exports.some((exportedSym) => exportedSym.name === name)) continue;\n\n if (CALL_EDGE_TYPES.has(node.type)) {\n const callers: SymbolCaller[] = [];\n for (const other of graph.nodes.values()) {\n for (const edge of other.callEdges ?? []) {\n if (edge.to === name) callers.push({ file: other.path, callerFunction: edge.from });\n }\n }\n matches.push({ path: node.path, category: node.category, precision: \"call\", callers });\n } else {\n const importers = getDependents(graph, node.path).map((dependent) => ({\n file: dependent.path,\n ...(dependent.symbols ? { symbols: dependent.symbols } : {}),\n }));\n matches.push({\n path: node.path,\n category: node.category,\n precision: \"file-level\",\n importers,\n });\n }\n }\n\n return matches;\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 { load as loadYaml } 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 = loadYaml(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","/** WorkspaceGraph holds one per-package Graph for a monorepo and exposes cross-package blast-radius queries. */\nimport type { FileNode } from \"../types/node\";\nimport { Graph } from \"./model\";\nimport type { WorkspacePackage } from \"./workspace\";\n\n/** @description JSON-safe snapshot of a `WorkspaceGraph`, suitable for writing to disk and restoring via `WorkspaceGraph.deserialize`. */\nexport interface SerializedWorkspaceGraph {\n monorepoRoot: string;\n type: string;\n packages: Array<{\n pkg: Omit<WorkspacePackage, \"root\">;\n nodes: FileNode[];\n }>;\n}\n\n/**\n * @description Holds one per-package `Graph` for each workspace package in a monorepo.\n * Cross-package import edges are preserved inside each graph via `ImportEdge.isWorkspace`.\n * The workspace graph does not merge all nodes into one flat namespace — each package graph\n * is queried independently, with cross-package traversal handled by `getAffectedAcrossPackages`.\n */\nexport class WorkspaceGraph {\n readonly packages: Map<string, { graph: Graph; pkg: WorkspacePackage }> = new Map();\n\n /**\n * @param {string} monorepoRoot - Absolute path to the monorepo root directory.\n * @param {string} type - Primary detected monorepo tool (e.g. `\"turborepo\"`, `\"pnpm\"`), or `\"none\"`.\n */\n constructor(\n readonly monorepoRoot: string,\n readonly type: string,\n ) {}\n\n /**\n * @description Registers a package and its pre-built graph into this workspace.\n * @param {WorkspacePackage} pkg - Package metadata including name, root, and entry points.\n * @param {Graph} graph - The fully-built dependency graph for this package.\n */\n addPackage(pkg: WorkspacePackage, graph: Graph): void {\n this.packages.set(pkg.name, { graph, pkg });\n }\n\n /**\n * @description Returns the workspace package whose `relativeRoot` is a path prefix of `relPath`.\n * @param {string} relPath - A monorepo-root-relative file path to look up.\n * @returns {WorkspacePackage | undefined} The owning package, or `undefined` if none matches.\n */\n getPackageForFile(relPath: string): WorkspacePackage | undefined {\n for (const { pkg } of this.packages.values()) {\n if (relPath === pkg.relativeRoot || relPath.startsWith(`${pkg.relativeRoot}/`)) {\n return pkg;\n }\n }\n return undefined;\n }\n\n /**\n * @description Returns a map of package-level dependencies derived from workspace import edges.\n * Key: package name. Value: list of workspace package names it imports from.\n * @returns {Map<string, string[]>} Map from package name to the list of workspace packages it depends on.\n */\n getPackageDependencies(): Map<string, string[]> {\n const deps = new Map<string, string[]>();\n for (const { graph, pkg } of this.packages.values()) {\n const pkgDeps = new Set<string>();\n for (const node of graph.nodes.values()) {\n for (const imp of node.imports) {\n if (imp.isWorkspace && imp.workspacePackage) {\n pkgDeps.add(imp.workspacePackage);\n }\n }\n }\n deps.set(pkg.name, [...pkgDeps]);\n }\n return deps;\n }\n\n /**\n * @description Cross-package blast-radius analysis. Returns every file (with its package name)\n * that could be affected if the given monorepo-root-relative path changes.\n * Step 1: traverses incoming edges within the owning package graph for intra-package dependents.\n * Step 2: surfaces files in other packages that hold workspace import edges pointing at the owner.\n * @param {string} relPath - Monorepo-root-relative path of the changed file.\n * @returns {Array<{ file: string; package: string }>} Each affected file paired with its package name.\n */\n getAffectedAcrossPackages(relPath: string): Array<{ file: string; package: string }> {\n const ownerPkg = this.getPackageForFile(relPath);\n if (!ownerPkg) return [];\n\n const ownerEntry = this.packages.get(ownerPkg.name);\n if (!ownerEntry) return [];\n\n const result: Array<{ file: string; package: string }> = [];\n\n // Intra-package dependents\n ownerEntry.graph.traverse(\n relPath,\n (node) => {\n if (node.path !== relPath) result.push({ file: node.path, package: ownerPkg.name });\n return true;\n },\n { direction: \"incoming\" },\n );\n\n // Cross-package: files in other packages that hold workspace imports into ownerPkg\n for (const { graph, pkg } of this.packages.values()) {\n if (pkg.name === ownerPkg.name) continue;\n for (const node of graph.nodes.values()) {\n const hasEdge = node.imports.some(\n (imp) => imp.isWorkspace && imp.workspacePackage === ownerPkg.name,\n );\n if (hasEdge) result.push({ file: node.path, package: pkg.name });\n }\n }\n\n return result;\n }\n\n /**\n * @description Serializes the workspace graph to a plain JSON-safe object.\n * `root` is omitted from package entries as it is not needed after build time.\n * @returns {SerializedWorkspaceGraph} A JSON-serializable snapshot of the workspace graph.\n */\n serialize(): SerializedWorkspaceGraph {\n return {\n monorepoRoot: this.monorepoRoot,\n type: this.type,\n packages: Array.from(this.packages.values()).map(({ graph, pkg }) => ({\n pkg: {\n name: pkg.name,\n relativeRoot: pkg.relativeRoot,\n entryPoints: pkg.entryPoints,\n },\n nodes: Array.from(graph.nodes.values()),\n })),\n };\n }\n\n /**\n * @description Reconstructs a `WorkspaceGraph` from a serialized snapshot.\n * The `root` field on each package is set to an empty string — it is not persisted and not needed for graph traversal.\n * @param {SerializedWorkspaceGraph} data - The plain object produced by `serialize`.\n * @returns {WorkspaceGraph} A fully functional `WorkspaceGraph` instance.\n */\n static deserialize(data: SerializedWorkspaceGraph): WorkspaceGraph {\n const wg = new WorkspaceGraph(data.monorepoRoot, data.type);\n for (const { pkg, nodes } of data.packages) {\n const nodeMap = new Map(nodes.map((node) => [node.path, node]));\n const graph = new Graph(nodeMap);\n wg.packages.set(pkg.name, {\n graph,\n pkg: { ...pkg, root: \"\" }, // root not persisted; not needed post-build\n });\n }\n return wg;\n }\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/**\n * @description Matches `NodeQuery.lastAuthor` against `FileNode.lastAuthor`. Nodes with no\n * author data fail the positive form and pass the negative (`!`-prefixed) form.\n */\nexport const matchLastAuthor: NodeMatcher = (node, query) => {\n if (!query.lastAuthor) return true;\n if (query.lastAuthor.startsWith(\"!\")) return node.lastAuthor !== query.lastAuthor.slice(1);\n return node.lastAuthor !== undefined && node.lastAuthor === query.lastAuthor;\n};\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/** @description Matches `NodeQuery.isDocumented` against whether `FileNode.documentedBy` is non-empty. */\nexport const matchIsDocumented: NodeMatcher = (node, query) =>\n query.isDocumented === undefined || !!node.documentedBy?.length === query.isDocumented;\n\n/** @description Matches `NodeQuery.isStale` against whether `FileNode.staleFor` is non-empty. */\nexport const matchIsStale: NodeMatcher = (node, query) =>\n query.isStale === undefined || !!node.staleFor?.length === query.isStale;\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/**\n * @description Matches `NodeQuery.minComplexity`. Nodes with no complexity data are excluded —\n * unlike `matchMinCoverage`'s large-sentinel convention (which inadvertently makes undata'd\n * nodes pass any threshold), this uses `-Infinity` so missing data always fails a `>=` check.\n */\nexport const matchMinComplexity: NodeMatcher = (node, query) =>\n query.minComplexity === undefined || (node.complexity ?? -Infinity) >= query.minComplexity;\n\n/** @description Matches `NodeQuery.maxComplexity`. Nodes with no complexity data are included (treated as 0). */\nexport const matchMaxComplexity: NodeMatcher = (node, query) =>\n query.maxComplexity === undefined || (node.complexity ?? 0) <= query.maxComplexity;\n\n/** @description Matches `NodeQuery.minCognitiveComplexity`. Nodes with no complexity data are excluded. */\nexport const matchMinCognitiveComplexity: NodeMatcher = (node, query) =>\n query.minCognitiveComplexity === undefined ||\n (node.cognitiveComplexity ?? -Infinity) >= query.minCognitiveComplexity;\n\n/** @description Matches `NodeQuery.maxCognitiveComplexity`. Nodes with no complexity data are included (treated as 0). */\nexport const matchMaxCognitiveComplexity: NodeMatcher = (node, query) =>\n query.maxCognitiveComplexity === undefined ||\n (node.cognitiveComplexity ?? 0) <= query.maxCognitiveComplexity;\n\n/** @description Matches `NodeQuery.minCommits`. Nodes with no git-stats data are excluded. */\nexport const matchMinCommits: NodeMatcher = (node, query) =>\n query.minCommits === undefined || (node.commitCount90d ?? -Infinity) >= query.minCommits;\n\n/** @description Matches `NodeQuery.maxCommits`. Nodes with no git-stats data are included (treated as 0). */\nexport const matchMaxCommits: NodeMatcher = (node, query) =>\n query.maxCommits === undefined || (node.commitCount90d ?? 0) <= query.maxCommits;\n\n/**\n * @description Matches `NodeQuery.any` — the node passes if it satisfies at least one sub-query\n * in the OR-group, ANDed with every other top-level criterion via the rest of `NODE_MATCHERS`.\n */\nexport const matchAny: NodeMatcher = (node, query, reverseIndex) =>\n !query.any?.length || query.any.some((subQuery) => matchNode(node, subQuery, reverseIndex));\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 matchLastAuthor,\n matchPath,\n matchIsExternal,\n matchTags,\n matchAllTags,\n matchImportsFile,\n matchImportedBy,\n matchMinImports,\n matchMaxImports,\n matchMinSize,\n matchMaxSize,\n matchHasDocstring,\n matchIsDocumented,\n matchIsStale,\n matchMinCoverage,\n matchMaxCoverage,\n matchMinExportUsage,\n matchMaxExportUsage,\n matchMinComplexity,\n matchMaxComplexity,\n matchMinCognitiveComplexity,\n matchMaxCognitiveComplexity,\n matchMinCommits,\n matchMaxCommits,\n matchAny,\n];\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. Lives here (not in `filter.ts`) so `matchAny`\n * can recurse into `NodeQuery.any` sub-queries without a circular import between the two modules.\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","/** Filters a graph by applying NodeQuery predicates: category, type, tag, path, imports, coverage, and more. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport { matchNode } from \"./matchers\";\nimport type { NodeQuery } from \"./types\";\n\nexport { matchNode } from \"./matchers\";\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 const direction = query.sortDir === \"asc\" ? -1 : 1;\n resultNodes.sort((nodeA, nodeB) => {\n let diff = 0;\n if (query.sort === \"size\") diff = nodeB.size - nodeA.size;\n else if (query.sort === \"imports\") diff = nodeB.imports.length - nodeA.imports.length;\n else if (query.sort === \"commitCount90d\")\n diff = (nodeB.commitCount90d ?? 0) - (nodeA.commitCount90d ?? 0);\n else if (query.sort === \"exportUsage\")\n diff = (nodeB.avgExportUsage ?? 0) - (nodeA.avgExportUsage ?? 0);\n else if (query.sort === \"complexity\")\n diff = (nodeB.complexity ?? 0) - (nodeA.complexity ?? 0);\n else if (query.sort === \"cognitiveComplexity\")\n diff = (nodeB.cognitiveComplexity ?? 0) - (nodeA.cognitiveComplexity ?? 0);\n return diff * direction;\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 and applies a single `key:value` clause onto `query`, mutating it in\n * place. Shared by the top-level comma-split loop and by each `|`-separated clause inside an\n * `any(...)` OR-group, so both contexts recognize exactly the same set of keys.\n * @param {NodeQuery} query - The query object to mutate with this clause's parsed value.\n * @param {string} part - A single `key:value` clause (no surrounding commas).\n * @returns {void}\n */\nfunction applyClause(query: NodeQuery, part: string): void {\n const colonIdx = part.indexOf(\":\");\n if (colonIdx === -1) return;\n const key = part.slice(0, colonIdx).trim().toLowerCase();\n const value = part.slice(colonIdx + 1).trim();\n if (!key || !value) return;\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\n | \"size\"\n | \"imports\"\n | \"commitCount90d\"\n | \"exportUsage\"\n | \"complexity\"\n | \"cognitiveComplexity\";\n break;\n case \"sortdir\":\n query.sortDir = value.toLowerCase() === \"asc\" ? \"asc\" : \"desc\";\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 case \"mincomplexity\":\n query.minComplexity = parseInt(value, 10);\n break;\n case \"maxcomplexity\":\n query.maxComplexity = parseInt(value, 10);\n break;\n case \"mincognitivecomplexity\":\n query.minCognitiveComplexity = parseInt(value, 10);\n break;\n case \"maxcognitivecomplexity\":\n query.maxCognitiveComplexity = parseInt(value, 10);\n break;\n case \"mincommits\":\n query.minCommits = parseInt(value, 10);\n break;\n case \"maxcommits\":\n query.maxCommits = parseInt(value, 10);\n break;\n case \"isdocumented\":\n query.isDocumented = value.toLowerCase() !== \"false\";\n break;\n case \"isstale\":\n query.isStale = value.toLowerCase() !== \"false\";\n break;\n case \"lastauthor\":\n query.lastAuthor = value;\n break;\n }\n}\n\n/**\n * @description Parses an `any(clause|clause|...)` OR-group token into an array of single-clause\n * sub-queries, each parsed independently via `applyClause`. Each `|`-separated clause is a\n * single `key:value` pair — multi-key sub-AND clauses inside a group are not supported.\n * @param {string} inner - The group contents, with the `any(`/`)` wrapper already stripped.\n * @returns {NodeQuery[]} One `NodeQuery` per non-empty `|`-separated clause.\n */\nfunction parseAnyGroup(inner: string): NodeQuery[] {\n const subQueries: NodeQuery[] = [];\n for (const clause of inner.split(\"|\")) {\n const trimmedClause = clause.trim();\n if (!trimmedClause) continue;\n const subQuery: NodeQuery = {};\n applyClause(subQuery, trimmedClause);\n if (Object.keys(subQuery).length > 0) subQueries.push(subQuery);\n }\n return subQueries;\n}\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 * A token of the form `any(key:val|key:val)` is parsed as an OR-group of single-key clauses\n * and accumulates into `query.any`, ANDed with every other top-level key in the string.\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 trimmedPart = part.trim();\n if (trimmedPart.toLowerCase().startsWith(\"any(\") && trimmedPart.endsWith(\")\")) {\n const inner = trimmedPart.slice(4, -1);\n const subQueries = parseAnyGroup(inner);\n if (subQueries.length > 0) query.any = [...(query.any ?? []), ...subQueries];\n continue;\n }\n applyClause(query, part);\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 { applyAtPrefixedTagProp, TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\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 return applyAtPrefixedTagProp(absPath, source, tags, \"tags\");\n }\n}\n","/** Shared TypeScript AST helpers for framework-specific tag injection strategies. */\nimport path from \"node:path\";\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/**\n * @description Bounds-checked array access. Throws instead of silently returning `undefined`\n * when `index` is out of range — used in place of `arr[index]!` so a violated invariant fails\n * loudly instead of propagating `undefined` into position math.\n * @param {readonly T[]} arr - Array to index into.\n * @param {number} index - Index expected to be in range.\n * @returns {T} The element at `index`.\n */\nfunction at<T>(arr: readonly T[], index: number): T {\n const value = arr[index];\n if (value === undefined) {\n throw new Error(`Index ${index} out of bounds (length ${arr.length})`);\n }\n return value;\n}\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 = at(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 = at(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 = at(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: at(args, i - 1).getEnd(), end: arg.getEnd(), text: \"\" };\n }\n\n const prop = at(arg.properties, idx);\n if (idx === arg.properties.length - 1) {\n // Last property — also remove the preceding comma\n return { start: at(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: at(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/**\n * @description Strips a leading `@` from each tag so previously injected, `@`-prefixed tags\n * can be compared against freshly computed (unprefixed) tag names.\n * @param {string[]} raw - Tag strings as read from the source, possibly `@`-prefixed.\n * @returns {string[]} The same tags with any leading `@` removed.\n */\nexport function stripAtPrefix(raw: string[]): string[] {\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\n/**\n * @description Shared `apply()` body for frameworks that store tags as a single array-valued\n * options property (e.g. Cypress's `tags`, Playwright's `tag`), each entry conventionally\n * prefixed with `@`. Parses the file, finds top-level describe/test/it calls, checks\n * idempotency against the existing (unprefixed) tag set, then injects or removes the property.\n * @param {string} absPath - Absolute file path (used for parser diagnostics only, not I/O).\n * @param {string} source - Current file contents.\n * @param {string[]} tags - Sorted, unique tag names to write. An empty array removes the property.\n * @param {string} propName - Name of the options property this framework uses (e.g. `\"tags\"` or `\"tag\"`).\n * @returns {string} Potentially modified source (equal to `source` when no change is needed).\n */\nexport function applyAtPrefixedTagProp(\n absPath: string,\n source: string,\n tags: string[],\n propName: string,\n): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n const [firstCall] = calls;\n if (!firstCall) return source;\n\n const rawExisting = readArrayProp(firstCall, propName, sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(stripAtPrefix(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const literal = toArrayLiteral(sortedTags.map((tag) => `@${tag}`));\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, propName, sf)\n : buildInjectReplacement(call, propName, literal, sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\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 `${[\"# <mokosh-tags>\", ...tags.map((tag) => `@${tag}`), \"# </mokosh-tags>\"].join(\"\\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 { applyAtPrefixedTagProp, TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\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 return applyAtPrefixedTagProp(absPath, source, tags, \"tag\");\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 lines.forEach((line, i) => {\n if (line.trimStart().startsWith(\"import \") || line.trimStart().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 const offset = lines.slice(0, lastImportLine + 1).reduce((sum, line) => sum + line.length + 1, 0); // +1 for \\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 const [firstCall] = calls;\n if (!firstCall) return stripped;\n\n // Idempotency check — if first call already has the exact sorted tags, nothing to do\n const existing = readArrayProp(firstCall, \"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 Piscina from \"piscina\";\nimport { type GitFileStats, getRepoGitStats } 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 enrichDocDrift,\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/** Directory names skipped by `walkProject`'s discovery passes (test files, docs), independent of `DEFAULT_IGNORE_DIRS` scanning. */\nconst WALK_IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n]);\n\n/** Below this many files, the worker-pool spin-up cost outweighs the parallelism benefit — parse in-process instead. */\nconst DEFAULT_MIN_FILES_FOR_POOL = 20;\n\n/** Configures whether/how `parseFile` calls are offloaded to a `piscina` worker pool. `false` always parses in-process. */\nexport type ParallelParsingOption = boolean | { minFiles?: number; maxThreads?: number };\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 walking the file system from a set of entry points.\n *\n * Responsibilities:\n * - Parsing each reachable source file via {@link parseFile} (optionally offloaded to a worker pool)\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 * **Traversal shape:** a file's imports are only known once it's been parsed, so discovery is\n * inherently incremental. Rather than a strict recursive depth-first walk, `build` runs a\n * queue-pumped wavefront: each round parses/resolves every currently-queued file in parallel\n * (`Promise.all`), and files discovered during that round join the next round's queue. This\n * lets `getNode` dispatch parses to a worker pool concurrently instead of one at a time. Nothing\n * downstream depends on discovery order, only on the `visited` set being consulted synchronously\n * before a file is queued (see {@link GraphBuilder.discover}), which keeps dedup race-free even\n * though many files are mid-parse at once.\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 queue: 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 private pool: Piscina | null = null;\n private gitStatsMap: Map<string, GitFileStats> | null = null;\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 * @param parallelParsing - Controls worker-pool offloading of `parseFile`. `true`/omitted enables it once a cheap pre-scan finds at least `minFiles` (default 20) files under `rootDir`; `false` always parses in-process; an object overrides `minFiles`/`maxThreads`.\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 private readonly parallelParsing: ParallelParsingOption = true,\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 seeds the queue; imports are resolved and unvisited local files join the\n * next wavefront round, continuing until the full reachable subgraph is covered. Test-node\n * tags are applied as a final post-processing step because they depend on the fully connected\n * graph (e.g. a file is \"test\" if something imports it with a `.test.` path, which can only be\n * 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\n await this.initPool();\n if (this.enableGitStats) {\n this.gitStatsMap = getRepoGitStats(this.rootDir);\n }\n try {\n for (const entryPath of entryPaths) this.enqueue(entryPath);\n await this.drain();\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 // Docs are never reachable from library entry points either (code doesn't import markdown),\n // and they commonly live outside the entry points' subtree entirely (top-level README, docs/),\n // so this scans the full rootDir rather than reusing the test-files' common-ancestor scope.\n await this.processDocFiles();\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 enrichDocDrift(this.graph.nodes);\n if (this.coverageMap.size > 0) enrichCoverage(this.graph.nodes, this.coverageMap);\n return new Graph(this.graph.nodes);\n } finally {\n if (this.pool) {\n await this.pool.destroy();\n this.pool = null;\n }\n }\n }\n\n /**\n * @description Decides whether to spin up a worker pool for this build and constructs it if\n * so. Skipped entirely when `parallelParsing` is `false`, or when a cheap pre-scan of\n * `rootDir` (capped at `minFiles`, so it never walks more than necessary) finds fewer than\n * `minFiles` files — small builds (the common unit-test case) never pay pool spin-up cost.\n * If pool construction itself throws (e.g. a sandboxed environment without `worker_threads`\n * permission), falls back to in-process parsing for the whole build instead of failing it.\n */\n private async initPool(): Promise<void> {\n if (this.parallelParsing === false) return;\n\n const opts = typeof this.parallelParsing === \"object\" ? this.parallelParsing : {};\n const minFiles = opts.minFiles ?? DEFAULT_MIN_FILES_FOR_POOL;\n if (minFiles > 0 && !this.hasAtLeastFiles(this.rootDir, minFiles)) return;\n\n try {\n const workerFilename = path.join(__dirname, \"parse-worker.js\");\n this.pool = new Piscina({\n filename: workerFilename,\n ...(opts.maxThreads !== undefined ? { maxThreads: opts.maxThreads } : {}),\n });\n } catch (err) {\n process.stderr.write(\n `\\nWarning: failed to start parse worker pool, falling back to synchronous parsing: ${err}\\n`,\n );\n this.pool = null;\n }\n }\n\n /**\n * @description Cheaply checks whether `dir` contains at least `threshold` files, stopping the\n * walk as soon as the threshold is met rather than counting exhaustively.\n * @param dir - Directory to scan (recursively, skipping `WALK_IGNORE_DIRS`).\n * @param threshold - Minimum file count to confirm.\n * @returns Whether at least `threshold` files were found.\n */\n private hasAtLeastFiles(dir: string, threshold: number): boolean {\n let count = 0;\n const stack = [dir];\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) continue;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(current, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!WALK_IGNORE_DIRS.has(entry.name)) stack.push(path.join(current, entry.name));\n } else if (entry.isFile()) {\n count++;\n if (count >= threshold) return true;\n }\n }\n }\n return count >= threshold;\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 matchesTest = (entry: fs.Dirent) =>\n patterns.some((pattern) => entry.name.includes(pattern));\n\n this.walkProject(scanRoot, matchesTest);\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()) this.walkProject(candidate, matchesTest);\n } catch {\n // not present\n }\n }\n dir = parent;\n }\n\n await this.drain();\n }\n\n /**\n * @description Scans the full project root for `.md`/`.mdx` files and processes them into the\n * graph. Docs are never reachable from library entry points (code doesn't import markdown) and,\n * unlike test files, aren't confined to the entry points' subtree — a top-level `README.md` or\n * `docs/` directory is common — so this walks `rootDir` directly rather than reusing\n * `processTestFiles`'s narrower common-ancestor scope.\n */\n private async processDocFiles(): Promise<void> {\n this.walkProject(\n this.rootDir,\n (entry) => entry.name.endsWith(\".md\") || entry.name.endsWith(\".mdx\"),\n );\n await this.drain();\n }\n\n /**\n * @description Recursively walks a directory, enqueueing every file that satisfies `matches`\n * for processing. Shared by `processTestFiles` and `processDocFiles`, whose discovery passes\n * only differ in which files they're looking for and where they start. Purely synchronous\n * directory traversal — the actual parse/resolve work happens later, in `drain`.\n * @param scanRoot - Directory to walk.\n * @param matches - Predicate tested against each file entry; matching files are enqueued.\n */\n private walkProject(scanRoot: string, matches: (entry: fs.Dirent) => boolean): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(scanRoot, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(scanRoot, entry.name);\n if (entry.isDirectory()) {\n if (!WALK_IGNORE_DIRS.has(entry.name)) this.walkProject(fullPath, matches);\n } else if (entry.isFile() && matches(entry)) {\n this.enqueue(fullPath);\n }\n }\n }\n\n /**\n * @description Marks `filePath` as visited and, if it wasn't already, queues it for\n * processing in the next `drain` round.\n *\n * The `visited` guard prevents re-processing files encountered via multiple import paths\n * (diamond dependencies). It is checked-and-set synchronously, with no `await` in between,\n * so concurrent callers mid-parse in the same `drain` round cannot race each other onto the\n * same file.\n * @param filePath - Absolute path of the file to enqueue.\n */\n private enqueue(filePath: string): void {\n if (this.visited.has(filePath)) return;\n this.visited.add(filePath);\n this.showProgress();\n this.queue.push(filePath);\n }\n\n /**\n * @description Drains the queue in wavefront rounds: every file currently queued is parsed\n * and resolved in parallel via `Promise.all`, and any new files discovered during that round\n * (via `resolveImports` → `enqueue`) form the next round's queue. Repeats until empty.\n */\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue;\n this.queue = [];\n await Promise.all(batch.map((filePath) => this.finishFile(filePath)));\n }\n }\n\n /**\n * @description Parses a single queued file and registers it in the graph, then enqueues its\n * local imports for the next wavefront round.\n * @param filePath - Absolute path of the file to process.\n */\n private async finishFile(filePath: string): Promise<void> {\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 enrichLibraryTags(node.imports, node.tags);\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 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 this.parseContent(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 Parses file content, dispatching to the worker pool when one is running for\n * this build, or parsing in-process otherwise. A worker's thrown error surfaces as a normal\n * rejected promise, so callers need no special handling for the pooled vs. in-process case.\n * @param filePath - Absolute path of the file, used to select the language parser.\n * @param content - Raw source content of the file.\n */\n private parseContent(filePath: string, content: string): ReturnType<typeof parseFile> {\n if (this.pool) {\n return this.pool.run({ filePath, content });\n }\n return parseFile(filePath, content);\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, by looking\n * up the repo-wide stats map computed once in {@link build} (see `getRepoGitStats`) rather than\n * spawning a `git log` per file. Silently no-ops for files not tracked by git.\n * @param node - The node to mutate in place.\n * @param relativePath - Project-relative path used as the lookup key.\n */\n private attachGitStats(node: FileNode, relativePath: string): void {\n const git = this.gitStatsMap?.get(relativePath);\n if (!git) return;\n node.commitCount90d = git.commitCount90d;\n if (git.lastAuthor !== undefined) node.lastAuthor = git.lastAuthor;\n if (git.lastCommitAt !== undefined) node.lastCommitAt = git.lastCommitAt;\n }\n\n /**\n * @description Resolves each import's raw specifier to a concrete path and, for local imports,\n * enqueues the target file for the next wavefront round.\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 this.enqueue(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 repo-wide commit activity stats via getRepoGitStats. */\nimport { execFileSync } 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 [\"diff\", \"--name-only\"],\n [\"diff\", \"--cached\", \"--name-only\"],\n [\"ls-files\", \"--others\", \"--exclude-standard\"],\n ];\n\n const allFiles = commands.flatMap((args) => {\n try {\n const output = execFileSync(\"git\", args, {\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\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 /** Unix timestamp (ms) of the most recent commit touching this file, regardless of the 90-day window. Used for doc-drift staleness comparisons. */\n lastCommitAt: number | undefined;\n}\n\n/** Null-byte-prefixed commit header emitted before each commit's file list, so header lines are unambiguous even when git's blank-line placement around `--name-status` output varies. */\nconst COMMIT_HEADER_FORMAT = \"%x00%H%x09%ae%x09%at\";\n\n/** `--name-status` output can be sizeable for repos with long history; the default 1MB execFileSync buffer is too small. */\nconst MAX_LOG_BUFFER_BYTES = 256 * 1024 * 1024;\n\n/**\n * @description Walks `git log --name-status` output (produced with {@link COMMIT_HEADER_FORMAT}) and\n * invokes `onFile` for every (file, author, timestamp) touch point, newest commit first. Rename\n * entries (`R<score>\\t<old>\\t<new>`) are attributed to the new path, approximating `--follow`\n * semantics in aggregate across the whole repo rather than per file.\n * @param output - Raw stdout from a `git log --name-status --format=<COMMIT_HEADER_FORMAT>` invocation.\n * @param onFile - Called once per file touched by each commit, in newest-to-oldest order.\n */\nfunction walkNameStatusLog(\n output: string,\n onFile: (filePath: string, authorEmail: string, timestampMs: number) => void,\n): void {\n let authorEmail = \"\";\n let timestampMs = 0;\n\n for (const line of output.split(\"\\n\")) {\n if (line.startsWith(\"\\u0000\")) {\n const [, email, at] = line.slice(1).split(\"\\t\");\n authorEmail = email ?? \"\";\n timestampMs = at ? Number(at) * 1000 : 0;\n continue;\n }\n if (line === \"\") continue;\n\n const parts = line.split(\"\\t\");\n const status = parts[0];\n if (!status) continue;\n const filePath = status.startsWith(\"R\") || status.startsWith(\"C\") ? parts[2] : parts[1];\n if (!filePath) continue;\n\n onFile(filePath, authorEmail, timestampMs);\n }\n}\n\n/**\n * @description Computes commit activity stats for every file in the repository with two `git log`\n * invocations total (independent of file count), instead of spawning per-file `git log` calls.\n * The first, bounded call covers `commitCount90d`/`lastAuthor`/`lastCommitAt` for files touched in\n * the last 90 days. The second, unbounded call only backfills `lastCommitAt` for files with no\n * recent history, since that field is used unconditionally for doc-staleness comparisons.\n * @param rootDir - Absolute path to the repository root, passed to `git -C` so the command works from any cwd.\n * @returns Map keyed by repo-relative path, as reported by git (matching `FileNode.path`). Empty if\n * `rootDir` isn't a git repository or git is unavailable.\n */\nexport function getRepoGitStats(rootDir: string): Map<string, GitFileStats> {\n const stats = new Map<string, GitFileStats>();\n\n try {\n const recentOutput = execFileSync(\n \"git\",\n [\n \"-C\",\n rootDir,\n \"log\",\n \"--since=90 days ago\",\n \"--name-status\",\n \"-M\",\n `--format=${COMMIT_HEADER_FORMAT}`,\n ],\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: MAX_LOG_BUFFER_BYTES },\n );\n\n walkNameStatusLog(recentOutput, (filePath, authorEmail, timestampMs) => {\n const existing = stats.get(filePath);\n if (existing) {\n existing.commitCount90d += 1;\n return;\n }\n stats.set(filePath, {\n commitCount90d: 1,\n lastAuthor: authorEmail || undefined,\n lastCommitAt: timestampMs || undefined,\n });\n });\n } catch {\n return stats;\n }\n\n try {\n const fullOutput = execFileSync(\n \"git\",\n [\"-C\", rootDir, \"log\", \"--name-status\", \"-M\", `--format=${COMMIT_HEADER_FORMAT}`],\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: MAX_LOG_BUFFER_BYTES },\n );\n\n walkNameStatusLog(fullOutput, (filePath, _authorEmail, timestampMs) => {\n if (stats.has(filePath)) return;\n stats.set(filePath, {\n commitCount90d: 0,\n lastAuthor: undefined,\n lastCommitAt: timestampMs || undefined,\n });\n });\n } catch {\n // Full-history fallback unavailable — files with no commits in the last 90 days simply lack lastCommitAt.\n }\n\n return stats;\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 case \".md\":\n case \".mdx\":\n return \"markdown\";\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, module exports, and tag annotations. */\nimport coffee from \"coffeescript\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\n\ninterface StringLiteralNode {\n value: string;\n originalValue?: string;\n}\n\ninterface CoffeeNode {\n constructor: { name: string };\n source?: StringLiteralNode;\n variable?: CoffeeNode & { base?: CoffeeNode; properties?: CoffeeNode[] };\n args?: Array<{ base?: StringLiteralNode }>;\n value?: unknown;\n base?: CoffeeNode;\n properties?: CoffeeNode[];\n name?: CoffeeNode;\n clause?: CoffeeNode;\n specifiers?: Array<{ identifier?: string; original?: { value?: string } }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Resolves the unquoted text of a CoffeeScript string-literal node. Prefers\n * `originalValue` (the compiler's own unescaped/unquoted form) and falls back to stripping\n * the surrounding quote characters from `.value` when `originalValue` isn't present.\n * @param node - The string-literal AST node, or `undefined`.\n * @returns The unquoted string content, or `undefined` if `node` has no string value.\n */\nfunction stringLiteralValue(node: StringLiteralNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.originalValue === \"string\") return node.originalValue;\n if (typeof node.value === \"string\") return stripQuotes(node.value);\n return undefined;\n}\n\n/**\n * @description Resolves the plain identifier name held by a CoffeeScript AST node, handling both\n * bare `IdentifierLiteral` nodes (`.value`) and `Value`-wrapped identifiers (`.base.value`) used\n * as the LHS of assignments and export clauses.\n * @param node - The AST node to resolve, or `undefined`.\n * @returns The identifier's string name, or `undefined` if `node` isn't a simple identifier.\n */\nfunction identifierName(node: CoffeeNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.value === \"string\") return node.value;\n if (typeof node.base?.value === \"string\") return node.base.value;\n return undefined;\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 = stringLiteralValue(node.source);\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 = stringLiteralValue(node.args?.[0]?.base);\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 Builds export symbols from a CommonJS `module.exports = { ... }` / `exports.foo = ...`\n * assignment's right-hand side: named exports for each field of an object literal, the class\n * name for `module.exports = class Foo`, or the referenced identifier's name for a bare\n * re-export (`module.exports = Widget`).\n * @param assignNode - The `Assign` AST node whose LHS already matched `module.exports` or `exports.<name>`.\n * @param propertyName - The member name accessed after `exports` (`baz` in `exports.baz = 3`), if any.\n * @returns Export symbols discovered on the assignment's right-hand side.\n */\nfunction exportsFromAssignment(\n assignNode: CoffeeNode,\n propertyName: string | undefined,\n): ExportedSymbol[] {\n if (propertyName) return [{ name: propertyName }];\n\n const rhs = assignNode.value as CoffeeNode | undefined;\n if (rhs?.constructor?.name === \"Class\") {\n const className = identifierName(rhs.variable);\n return className ? [{ name: className }] : [];\n }\n if (rhs?.base?.constructor?.name === \"Obj\" && Array.isArray(rhs.base.properties)) {\n // `foo: value` properties are Assign nodes (name via `.variable`); shorthand `{ foo }`\n // properties compile to a bare Value/IdentifierLiteral node instead.\n return rhs.base.properties\n .map((property) => identifierName(property.variable) ?? identifierName(property))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const bareName = identifierName(rhs);\n return bareName ? [{ name: bareName }] : [];\n}\n\n/**\n * @description Builds export symbols from an ES `export ...` declaration clause: `export { a, b }`\n * specifier lists, `export foo = ...` assignments, and any other clause shape carrying a\n * `.variable` (e.g. `export class Foo`).\n * @param clause - The `clause` payload of an `ExportNamedDeclaration` node.\n * @returns Export symbols discovered in the clause.\n */\nfunction exportsFromNamedClause(clause: CoffeeNode): ExportedSymbol[] {\n if (Array.isArray(clause.specifiers)) {\n return clause.specifiers\n .map((specifier) => specifier.identifier ?? specifier.original?.value)\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const name = identifierName(clause.variable) ?? identifierName(clause.variable?.base);\n return name ? [{ name }] : [];\n}\n\n/**\n * @description Inspects a single CoffeeScript AST node and appends any discovered import edge or\n * export symbol to the corresponding accumulator. Handles `ImportDeclaration`/`Call` (require)\n * for imports, and `module.exports`/`exports.<name>` assignments plus ES `export` declarations\n * for exports.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to inspect.\n * @param imports - Accumulator array that receives any discovered import edge.\n * @param exports - Accumulator array that receives any discovered export symbols.\n */\nfunction visitNode(\n filePath: string,\n node: CoffeeNode,\n imports: ImportEdge[],\n exports: ExportedSymbol[],\n): void {\n const className = node.constructor?.name;\n if (className === \"ImportDeclaration\") {\n const edge = edgeFromImportDeclaration(filePath, node);\n if (edge) imports.push(edge);\n } else if (className === \"Call\") {\n const edge = edgeFromRequireCall(filePath, node);\n if (edge) imports.push(edge);\n } else if (className === \"Assign\") {\n const base = identifierName(node.variable?.base);\n const propertyName = node.variable?.properties?.[0]?.name?.value;\n if (base === \"module\" && propertyName === \"exports\") {\n exports.push(...exportsFromAssignment(node, undefined));\n } else if (base === \"exports\" && typeof propertyName === \"string\") {\n exports.push(...exportsFromAssignment(node, propertyName));\n }\n } else if (className === \"ExportNamedDeclaration\" && node.clause) {\n exports.push(...exportsFromNamedClause(node.clause));\n } else if (className === \"ExportDefaultDeclaration\") {\n exports.push({ name: \"default\" });\n }\n}\n\n/**\n * @description Recursively walks the CoffeeScript AST and collects all import edges and export\n * symbols into the given accumulators. Skips `locationData` keys to prevent infinite cycles on\n * circular metadata references.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to walk.\n * @param imports - Accumulator array that receives all discovered import edges.\n * @param exports - Accumulator array that receives all discovered export symbols.\n */\nfunction traverse(\n filePath: string,\n node: CoffeeNode,\n imports: ImportEdge[],\n exports: ExportedSymbol[],\n): void {\n if (!node || typeof node !== \"object\") return;\n visitNode(filePath, node, imports, exports);\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, imports, exports);\n } else {\n traverse(filePath, child as CoffeeNode, imports, exports);\n }\n }\n}\n\n/**\n * @description Parses a CoffeeScript source file and extracts its import edges, module exports,\n * comment-marker tags, and file category. Uses the CoffeeScript compiler's `nodes()` API for\n * full AST traversal, capturing ES `import`/`export` declarations as well as CommonJS\n * `require()`/`module.exports` conventions. Falls back to empty imports/exports if the file\n * 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, exports, extracted tags, and resolved category.\n */\nexport function parseCoffeeScript(filePath: string, content: string): ParseResult {\n const tags = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tags);\n const imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n\n try {\n traverse(filePath, coffee.nodes(content) as unknown as CoffeeNode, imports, exports);\n } catch (_e) {\n // coffeescript compiler throws on invalid syntax; return what we have\n }\n\n const seenNames = new Set<string>();\n const dedupedExports = exports.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n\n return {\n imports,\n exports: dedupedExports,\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\n/**\n * @description Scans raw source text for `@tag <name>` comment annotations and collects the\n * tag names. Shared across languages whose parser has no dedicated comment/annotation AST\n * node (CoffeeScript, LiveScript, Lua), so tags are extracted via regex ahead of/instead of\n * full parsing. 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 */\nexport function extractTagAnnotations(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 an explicit `@tag test` annotation. Shared\n * classification rule for the regex-tagged languages (CoffeeScript, LiveScript, Lua).\n * @param filePath - Path to the file being classified.\n * @param tags - Tag names extracted from the file's content (see `extractTagAnnotations`).\n * @returns `\"test\"` if the file is a test file, `\"logic\"` otherwise.\n */\nexport function classifyTestOrLogic(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","/** 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. */\n\nimport path from \"node:path\";\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport { parser } from \"@lezer/go\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { collectFunctionComplexity, computeComplexity, receiverTypeName } from \"../complexity/go\";\nimport type { ParseResult, RawCallEdge } 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 const packageIdents = new Map<string, 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 // Track the identifier code uses to reference this package: an explicit alias\n // (DefName), or — absent one — the conventional last path segment.\n const aliasNode = cursor.node.getChild(\"DefName\");\n const ident = aliasNode\n ? content.slice(aliasNode.from, aliasNode.to)\n : specifier.split(\"/\").pop();\n if (ident && ident !== \"_\" && ident !== \".\") packageIdents.set(ident, specifier);\n }\n break;\n }\n\n case \"FunctionDecl\":\n case \"MethodDecl\":\n case \"TypeDecl\":\n case \"VarDecl\":\n case \"ConstDecl\": {\n // For FunctionDecl the DefName is a direct child.\n // For MethodDecl the receiver name is a direct child too, so match FieldName instead.\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(\"FieldName\") ??\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 const { complexity, cognitiveComplexity } = computeComplexity(tree.topNode, content);\n const functions = collectFunctionComplexity(tree, content);\n const rawCallEdges = category === \"test\" ? [] : collectRawCallEdges(tree, content, packageIdents);\n\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 rawCallEdges,\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\n };\n}\n\n/**\n * @description Walks every top-level exported `FunctionDecl` and every `MethodDecl` (regardless\n * of export — a receiver always names the enclosing type, mirroring how the TS parser tracks\n * class methods regardless of their own visibility), recording a `RawCallEdge` for each call to\n * a package-qualified function (`pkg.Func()`) where `pkg` resolves to a tracked import.\n * Unqualified calls (to same-file functions) are not cross-file dependencies and are skipped.\n * @param {Tree} tree - The parsed @lezer/go tree.\n * @param {string} content - Full source text.\n * @param {Map<string, string>} packageIdents - Maps the identifier code uses for a package\n * (alias or default last-path-segment) to its raw import specifier.\n * @returns {RawCallEdge[]} One edge per package-qualified call to a known imported package.\n */\nfunction collectRawCallEdges(\n tree: Tree,\n content: string,\n packageIdents: Map<string, string>,\n): RawCallEdge[] {\n const edges: RawCallEdge[] = [];\n\n function walkBody(node: SyntaxNode, callerName: string): void {\n if (node.type.name === \"CallExpr\") {\n const callee = node.firstChild;\n if (callee?.type.name === \"SelectorExpr\") {\n const pkgIdentNode = callee.firstChild;\n const fieldNode = callee.getChild(\"FieldName\");\n if (pkgIdentNode?.type.name === \"VariableName\" && fieldNode) {\n const pkgIdent = content.slice(pkgIdentNode.from, pkgIdentNode.to);\n const toSpecifier = packageIdents.get(pkgIdent);\n if (toSpecifier) {\n const to = content.slice(fieldNode.from, fieldNode.to);\n edges.push({ from: callerName, to, toSpecifier });\n }\n }\n }\n }\n let child = node.firstChild;\n while (child) {\n walkBody(child, callerName);\n child = child.nextSibling;\n }\n }\n\n const cursor = tree.cursor();\n do {\n if (cursor.name === \"FunctionDecl\") {\n const nameNode = cursor.node.getChild(\"DefName\");\n const body = cursor.node.getChild(\"Block\");\n if (nameNode && body) {\n const name = content.slice(nameNode.from, nameNode.to);\n if (/^[A-Z]/.test(name)) walkBody(body, name);\n }\n } else if (cursor.name === \"MethodDecl\") {\n const fieldNode = cursor.node.getChild(\"FieldName\");\n const body = cursor.node.getChild(\"Block\");\n if (fieldNode && body) {\n const methodName = content.slice(fieldNode.from, fieldNode.to);\n const receiver = receiverTypeName(cursor.node, content);\n walkBody(body, receiver ? `${receiver}.${methodName}` : methodName);\n }\n }\n } while (cursor.next());\n\n return edges;\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","/** Shared low-level helpers for walking @lezer/common SyntaxNode trees during complexity\n * analysis. The actual cyclomatic/cognitive scoring logic in ./go.ts and ./python.ts is *not*\n * shared beyond this: Go's if/else-if is nested (like TS), while Python's if/elif/else and\n * try/except are flat sibling sequences within one node — different enough that a forced common\n * abstraction would need as much branching as it saves. See docs/adr-011-go-python-call-edges.md. */\nimport type { SyntaxNode } from \"@lezer/common\";\n\n/**\n * @description Collects the direct children of a Lezer syntax node into an array, in document\n * order, by walking the `firstChild`/`nextSibling` chain. Lezer nodes have no `getChildren()`\n * equivalent for \"all children\", unlike the TS compiler API's `forEachChild`.\n * @param {SyntaxNode} node - The node whose direct children to collect.\n * @returns {SyntaxNode[]} The node's direct children, in order.\n */\nexport function childrenOf(node: SyntaxNode): SyntaxNode[] {\n const result: SyntaxNode[] = [];\n let child = node.firstChild;\n while (child) {\n result.push(child);\n child = child.nextSibling;\n }\n return result;\n}\n\n/**\n * @description Resolves the 1-indexed source line a byte offset falls on by counting newlines\n * before it. @lezer trees carry no line/column info by default, unlike the TS compiler API's\n * `SourceFile.getLineAndCharacterOfPosition`.\n * @param {string} content - Full source text.\n * @param {number} pos - Byte offset into `content`.\n * @returns {number} The 1-indexed line number containing `pos`.\n */\nexport function lineAt(content: string, pos: number): number {\n let line = 1;\n for (let i = 0; i < pos && i < content.length; i++) {\n if (content[i] === \"\\n\") line++;\n }\n return line;\n}\n","/** Computes McCabe cyclomatic complexity and cognitive complexity for Go source, mirroring the\n * TypeScript algorithm in ../complexity.ts but walking the @lezer/go SyntaxNode tree instead. */\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport type { FunctionComplexity } from \"../../types/node\";\nimport { childrenOf, lineAt } from \"./lezer-utils\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for a Go AST node: every independent\n * decision point counts (base 1) — `if`, `for`, non-default `switch`/`select` cases, and each\n * `&&` / `||` operator. Go has no `try`/`catch` or ternary operator, so those TS decision\n * points have no Go equivalent.\n * @param {SyntaxNode} rootNode - The AST root node to analyse — the whole file's top node for\n * file-level totals, or a `FunctionDecl`/`MethodDecl`/`FunctionLiteral` node to score it alone.\n * @param {string} content - Full source text, used to read operator token text.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: SyntaxNode, content: string): number {\n let complexity = 1;\n\n function walk(node: SyntaxNode): void {\n switch (node.type.name) {\n case \"IfStatement\":\n case \"ForStatement\":\n complexity++;\n break;\n case \"Case\": {\n // Case covers both `case` and `default` clauses; only `case` is a decision point.\n if (node.firstChild?.type.name === \"case\") complexity++;\n break;\n }\n case \"LogicOp\": {\n const text = content.slice(node.from, node.to);\n if (text === \"&&\" || text === \"||\") complexity++;\n break;\n }\n }\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for a Go AST\n * node, tracking how hard the code is to read by adding a nesting penalty. Mirrors the\n * TypeScript algorithm: `if`/`for`/`switch` increment by `1 + current nesting depth` and\n * increase depth for their children; chained `else if` gets +1 flat; a bare `else` gets +1\n * flat; `&&`/`||` each add +1 flat; nested function literals add `1 + depth`.\n * @param {SyntaxNode} rootNode - The AST root node to analyse (nesting depth resets to 0 here).\n * @param {string} content - Full source text, used to read operator token text.\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: SyntaxNode, content: string): number {\n let cognitive = 0;\n\n function walk(node: SyntaxNode, depth: number, isElseIf: boolean): void {\n const name = node.type.name;\n\n if (name === \"IfStatement\") {\n cognitive += isElseIf ? 1 : 1 + depth;\n const kids = childrenOf(node);\n const bodyDepth = isElseIf ? depth : depth + 1;\n\n const cond = kids[1];\n if (cond && cond.type.name !== \"Block\") walk(cond, bodyDepth, false);\n\n const thenBlock = kids.find((k) => k.type.name === \"Block\");\n if (thenBlock) walk(thenBlock, bodyDepth, false);\n\n const elseIndex = kids.findIndex((k) => k.type.name === \"else\");\n if (elseIndex >= 0) {\n const after = kids[elseIndex + 1];\n if (after?.type.name === \"IfStatement\") {\n walk(after, depth, true);\n } else if (after) {\n cognitive += 1;\n walk(after, depth + 1, false);\n }\n }\n return;\n }\n\n if (name === \"ForStatement\" || name === \"SwitchStatement\" || name === \"SelectStatement\") {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1, false);\n child = child.nextSibling;\n }\n return;\n }\n\n if (name === \"LogicOp\") {\n const text = content.slice(node.from, node.to);\n if (text === \"&&\" || text === \"||\") cognitive += 1;\n }\n\n const isNestedFunctionLiteral = depth > 0 && name === \"FunctionLiteral\";\n if (isNestedFunctionLiteral) {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1, false);\n child = child.nextSibling;\n }\n return;\n }\n\n let child = node.firstChild;\n while (child) {\n walk(child, depth, false);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode, 0, false);\n return cognitive;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and cognitive complexity for a Go AST\n * node by composing `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {SyntaxNode} node - The AST root node to analyse.\n * @param {string} content - Full source text.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores.\n */\nexport function computeComplexity(\n node: SyntaxNode,\n content: string,\n): { complexity: number; cognitiveComplexity: number } {\n return {\n complexity: computeCyclomaticComplexity(node, content),\n cognitiveComplexity: computeCognitiveComplexity(node, content),\n };\n}\n\n/**\n * @description Reads the receiver type name off a `MethodDecl` node (e.g. `Receiver` from\n * `func (r *Receiver) Method()`), unwrapping a pointer receiver if present.\n * @param {SyntaxNode} methodDecl - The `MethodDecl` node.\n * @param {string} content - Full source text, used to slice the type name.\n * @returns {string | undefined} The receiver's bare type name, or `undefined` if not found.\n */\nexport function receiverTypeName(methodDecl: SyntaxNode, content: string): string | undefined {\n const receiverParams = methodDecl.getChild(\"Parameters\");\n const receiverParam = receiverParams?.getChild(\"Parameter\");\n const typeNode =\n receiverParam?.getChild(\"TypeName\") ??\n receiverParam?.getChild(\"PointerType\")?.getChild(\"TypeName\");\n return typeNode ? content.slice(typeNode.from, typeNode.to) : undefined;\n}\n\n/**\n * @description Walks the entire Go source tree and records complexity for every named\n * `FunctionDecl` and `MethodDecl` — top-level or nested. Methods are qualified as\n * `ReceiverType.MethodName` to mirror the TS parser's `ClassName.methodName` convention.\n * @param {Tree} tree - The parsed @lezer/go tree.\n * @param {string} content - Full source text.\n * @returns {FunctionComplexity[]} Per-function complexity entries, in traversal order.\n */\nexport function collectFunctionComplexity(tree: Tree, content: string): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n const cursor = tree.cursor();\n\n do {\n if (cursor.name === \"FunctionDecl\") {\n const nameNode = cursor.node.getChild(\"DefName\");\n if (nameNode) {\n const name = content.slice(nameNode.from, nameNode.to);\n const { complexity, cognitiveComplexity } = computeComplexity(cursor.node, content);\n results.push({ name, line: lineAt(content, cursor.from), complexity, cognitiveComplexity });\n }\n } else if (cursor.name === \"MethodDecl\") {\n const nameNode = cursor.node.getChild(\"FieldName\");\n if (nameNode) {\n const methodName = content.slice(nameNode.from, nameNode.to);\n const receiver = receiverTypeName(cursor.node, content);\n const name = receiver ? `${receiver}.${methodName}` : methodName;\n const { complexity, cognitiveComplexity } = computeComplexity(cursor.node, content);\n results.push({ name, line: lineAt(content, cursor.from), complexity, cognitiveComplexity });\n }\n }\n } while (cursor.next());\n\n return results;\n}\n","/** Parses LiveScript files to extract import edges, module exports, and tag annotations. */\n// @ts-expect-error\nimport ls from \"livescript\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\n\ninterface LiveScriptNode {\n constructor: { name: string };\n type?: string;\n value?: string;\n right?: LiveScriptNode;\n left?: LiveScriptNode;\n head?: LiveScriptNode & { value?: string; verb?: string };\n base?: LiveScriptNode;\n verb?: string;\n key?: LiveScriptNode & { name?: string };\n name?: string;\n title?: LiveScriptNode;\n items?: LiveScriptNode[];\n val?: LiveScriptNode;\n tails?: Array<{\n constructor: { name: string };\n type?: string;\n args?: Array<{ value: string }>;\n key?: LiveScriptNode & { name?: string };\n }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Resolves the plain identifier name held by a LiveScript AST node, handling bare\n * `Var` nodes (`.value`) and `Value`-wrapped identifiers (`.base.value`).\n * @param node - The AST node to resolve, or `undefined`.\n * @returns The identifier's string name, or `undefined` if `node` isn't a simple identifier.\n */\nfunction identifierName(node: LiveScriptNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.value === \"string\") return node.value;\n if (typeof node.base?.value === \"string\") return node.base.value;\n return undefined;\n}\n\n/**\n * @description Builds export symbols from the right-hand side of a `module.exports = ...` /\n * `export <name> = ...` assignment: named exports for each field of an object literal, the\n * class name for `= class Foo`, or the referenced identifier's name for a bare re-export.\n * @param rhs - The assignment's right-hand-side AST node.\n * @returns Export symbols discovered on the right-hand side.\n */\nfunction exportsFromValue(rhs: LiveScriptNode | undefined): ExportedSymbol[] {\n if (!rhs) return [];\n if (rhs.constructor?.name === \"Class\") {\n const className = identifierName(rhs.title);\n return className ? [{ name: className }] : [];\n }\n if (rhs.constructor?.name === \"Obj\" && Array.isArray(rhs.items)) {\n return rhs.items\n .map((prop) => identifierName(prop.key) ?? identifierName(prop.val))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const bareName = identifierName(rhs);\n return bareName ? [{ name: bareName }] : [];\n}\n\n/**\n * @description Inspects a single AST node and returns export symbols if the node represents a\n * `module.exports = ...` / `exports.<name> = ...` CommonJS assignment, or an `export <decl>` /\n * `export {a, b}` LiveScript declaration (both compile through the `out` verb), or an empty\n * array otherwise.\n * @param node - The AST node to inspect.\n * @returns Export symbols discovered on this node, if any.\n */\nfunction extractExports(node: LiveScriptNode): ExportedSymbol[] {\n const type = node.constructor?.name || node.type;\n\n if (type === \"Assign\" && node.left?.constructor?.name === \"Chain\") {\n const chain = node.left;\n const base = identifierName(chain.head);\n const tailIndex = chain.tails?.[0];\n const propertyName = tailIndex?.key?.name;\n\n if (base === \"module\" && propertyName === \"exports\") {\n return exportsFromValue(node.right);\n }\n if (base === \"exports\" && typeof propertyName === \"string\") {\n return [{ name: propertyName }];\n }\n if (chain.head?.verb === \"out\" && typeof propertyName === \"string\") {\n return [{ name: propertyName }];\n }\n }\n\n if (type === \"Import\" && node.left?.verb === \"out\") {\n const rhs = node.right;\n if (rhs?.constructor?.name === \"Obj\" && Array.isArray(rhs.items)) {\n return rhs.items\n .map((prop) => identifierName(prop.val) ?? identifierName(prop.key))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n }\n\n return [];\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 and export\n * symbols found 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 * @param exports - Accumulator array that receives all discovered export symbols.\n * @returns All ImportEdge values found in this node and its descendants.\n */\nfunction collectEdges(\n node: LiveScriptNode,\n filePath: string,\n exports: ExportedSymbol[],\n): 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 exports.push(...extractExports(node));\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, exports));\n } else {\n edges.push(...collectEdges(child as LiveScriptNode, filePath, exports));\n }\n }\n\n return edges;\n}\n\n/**\n * @description Parses a LiveScript source file to extract its dependency edges, module exports,\n * comment-marker tags, and file category. Handles both ES-style `import` statements and\n * CommonJS `require()` calls, plus the `module.exports`/`exports.<name>` and `export ...`\n * conventions. 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, exports, extracted tags, and the file category.\n */\nexport function parseLiveScript(filePath: string, content: string): ParseResult {\n const tags = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tags);\n let imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n\n try {\n imports = collectEdges(ls.ast(content) as LiveScriptNode, filePath, exports);\n } catch (_e) {\n // ignore parse errors\n }\n\n const seenNames = new Set<string>();\n const dedupedExports = exports.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n\n return {\n imports,\n exports: dedupedExports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Lua source files via luaparse to extract require() dependency edges, module exports, and @tag annotations. */\nimport type { Chunk, Node, Statement } from \"luaparse\";\nimport luaparse from \"luaparse\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\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 Collects the names of top-level local variables initialized as an empty (or any)\n * table constructor — the conventional `local M = {}` module-table idiom — so that later\n * assignments to `M.<field>` can be recognized as module exports.\n * @param topLevelStatements - Statements at the root of the chunk body.\n * @returns Set of identifier names bound to a table constructor at the top level.\n */\nfunction collectModuleTableNames(topLevelStatements: Statement[]): Set<string> {\n const moduleTableNames = new Set<string>();\n for (const statement of topLevelStatements) {\n if (statement.type !== \"LocalStatement\") continue;\n statement.variables.forEach((variable, index) => {\n const initializer = statement.init[index];\n if (initializer?.type === \"TableConstructorExpression\") {\n moduleTableNames.add(variable.name);\n }\n });\n }\n return moduleTableNames;\n}\n\n/**\n * @description Extracts one export symbol per field of a `return { ... }` table literal, the\n * other conventional Lua export idiom alongside the `local M = {}` module table.\n * @param topLevelStatements - Statements at the root of the chunk body.\n * @returns Export symbols named after each `TableKeyString` field in the trailing return table.\n */\nfunction collectReturnTableExports(topLevelStatements: Statement[]): ExportedSymbol[] {\n const lastStatement = topLevelStatements[topLevelStatements.length - 1];\n if (lastStatement?.type !== \"ReturnStatement\") return [];\n const returnedTable = lastStatement.arguments[0];\n if (returnedTable?.type !== \"TableConstructorExpression\") return [];\n\n const exportedSymbols: ExportedSymbol[] = [];\n for (const field of returnedTable.fields) {\n if (field.type === \"TableKeyString\") exportedSymbols.push({ name: field.key.name });\n }\n return exportedSymbols;\n}\n\n/**\n * @description Walks the top-level chunk statements to collect Lua module exports: fields\n * assigned onto a `local M = {}` module table (via `function M.foo()` or `M.foo = ...`),\n * fields of a trailing `return { ... }` table literal, and non-local top-level function\n * declarations (which are visible as Lua globals).\n * @param ast - The parsed luaparse AST root.\n * @returns One export symbol per discovered module member.\n */\nfunction collectExports(ast: Chunk): ExportedSymbol[] {\n const topLevelStatements = ast.body;\n const moduleTableNames = collectModuleTableNames(topLevelStatements);\n const exportedSymbols: ExportedSymbol[] = collectReturnTableExports(topLevelStatements);\n\n for (const statement of topLevelStatements) {\n if (statement.type === \"FunctionDeclaration\") {\n if (statement.identifier?.type === \"MemberExpression\") {\n if (\n statement.identifier.base.type === \"Identifier\" &&\n moduleTableNames.has(statement.identifier.base.name)\n ) {\n exportedSymbols.push({ name: statement.identifier.identifier.name });\n }\n } else if (statement.identifier?.type === \"Identifier\" && !statement.isLocal) {\n exportedSymbols.push({ name: statement.identifier.name });\n }\n } else if (statement.type === \"AssignmentStatement\") {\n for (const variable of statement.variables) {\n if (\n variable.type === \"MemberExpression\" &&\n variable.base.type === \"Identifier\" &&\n moduleTableNames.has(variable.base.name)\n ) {\n exportedSymbols.push({ name: variable.identifier.name });\n }\n }\n }\n }\n\n const seenNames = new Set<string>();\n return exportedSymbols.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n}\n\n/**\n * @description Parses a Lua source file using luaparse to extract `require()` dependency edges,\n * module exports (the `local M = {}` / `return { ... }` idioms plus global function\n * declarations), and `@tag` comment annotations. Falls back to empty imports/exports if the\n * file contains 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, exports, extracted tags, and resolved category.\n */\nexport function parseLua(filePath: string, content: string): ParseResult {\n const tagNames = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tagNames);\n\n let imports: ImportEdge[] = [];\n let exports: ExportedSymbol[] = [];\n try {\n const ast: Chunk = luaparse.parse(content);\n imports = collectRequireEdges(ast, filePath);\n exports = collectExports(ast);\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 Markdown/MDX files using remark to extract references to project files as import edges. */\nimport type { ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\n/** Minimal shape of an mdast node — avoids depending on `@types/mdast` for a handful of fields. */\ninterface MdastNode {\n type: string;\n url?: string;\n value?: string;\n children?: MdastNode[];\n}\n\nconst EXTERNAL_LINK_PREFIXES = [\"http://\", \"https://\", \"mailto:\", \"//\"];\n\nconst CODE_EXTENSIONS =\n \"ts|tsx|js|jsx|mjs|cjs|py|go|lua|css|scss|less|styl|coffee|ls|feature|md|mdx|json\";\nconst PATH_TOKEN_PATTERN = new RegExp(\n `(?:\\\\.{1,2}/)?[\\\\w.-]+(?:/[\\\\w.-]+)*\\\\.(?:${CODE_EXTENSIONS})\\\\b`,\n \"g\",\n);\n\nlet processorPromise: Promise<{ parse(content: string): MdastNode }> | undefined;\n\n/**\n * @description Lazily creates and caches a unified processor configured with `remark-parse`.\n * Both packages are ESM-only, so they're loaded via dynamic import from this CommonJS codebase.\n * @returns A processor whose synchronous `parse()` yields an mdast tree.\n */\nasync function getProcessor(): Promise<{ parse(content: string): MdastNode }> {\n processorPromise ??= (async () => {\n const { unified } = await import(\"unified\");\n const remarkParse = (await import(\"remark-parse\")).default;\n return unified().use(remarkParse) as unknown as { parse(content: string): MdastNode };\n })();\n return processorPromise;\n}\n\n/**\n * @description Returns true when a markdown link target points outside the project\n * (web URL, mailto, protocol-relative) or is a same-page anchor rather than a file reference.\n * @param url - The raw `url` field from an mdast `link` node.\n * @returns `true` if the link should be skipped rather than treated as a file reference.\n */\nfunction isExternalLink(url: string): boolean {\n const trimmed = url.trim();\n return (\n trimmed.length === 0 ||\n trimmed.startsWith(\"#\") ||\n EXTERNAL_LINK_PREFIXES.some((p) => trimmed.startsWith(p))\n );\n}\n\n/**\n * @description Scans the text of a code span or fenced code block for path-like tokens\n * (e.g. `src/auth/reset.ts`), since docs commonly reference files this way in prose.\n * @param text - Raw text content of an mdast `code` or `inlineCode` node.\n * @param filePath - Path of the markdown file being parsed, used as `fromPath` on each edge.\n * @returns One `ImportEdge` per distinct path-like token found.\n */\nfunction edgesFromCodeText(text: string, filePath: string): ImportEdge[] {\n const matches = text.match(PATH_TOKEN_PATTERN);\n if (!matches) return [];\n return matches.map((specifier) => ({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: false,\n type: \"static\" as const,\n }));\n}\n\n/**\n * @description Recursively walks an mdast tree collecting candidate file references from\n * `link` node URLs and `code`/`inlineCode` node text.\n * @param node - Current mdast node (root or any content node).\n * @param filePath - Path of the markdown file being parsed, used as `fromPath` on each edge.\n * @param edges - Accumulator array mutated in place.\n */\nfunction walk(node: MdastNode, filePath: string, edges: ImportEdge[]): void {\n if (node.type === \"link\" && typeof node.url === \"string\" && !isExternalLink(node.url)) {\n edges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: node.url,\n isStyle: false,\n type: \"static\",\n });\n }\n if ((node.type === \"code\" || node.type === \"inlineCode\") && typeof node.value === \"string\") {\n edges.push(...edgesFromCodeText(node.value, filePath));\n }\n if (Array.isArray(node.children)) {\n for (const child of node.children) walk(child, filePath, edges);\n }\n}\n\n/**\n * @description Parses a Markdown/MDX file into a `ParseResult` whose `imports` are candidate\n * references to project files (from links and code spans/blocks). Markdown has no export or\n * tag concept, so those arrays are always empty — mirroring the style-parser precedent.\n * @param filePath - Path of the markdown file being parsed.\n * @param content - Raw markdown source.\n * @returns A `ParseResult` with deduplicated import edges and `category: \"other\"`.\n */\nexport async function parseMarkdown(filePath: string, content: string): Promise<ParseResult> {\n const processor = await getProcessor();\n const tree = processor.parse(content);\n\n const edges: ImportEdge[] = [];\n walk(tree, filePath, edges);\n\n const seen = new Set<string>();\n const imports = edges.filter((edge) => {\n if (seen.has(edge.rawSpecifier)) return false;\n seen.add(edge.rawSpecifier);\n return true;\n });\n\n return { imports, exports: [], tags: [], category: \"other\" };\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, Tree } from \"@lezer/common\";\nimport { parser } from \"@lezer/python\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { collectFunctionComplexity, computeComplexity } from \"../complexity/python\";\nimport type { ParseResult, RawCallEdge } 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 const { complexity, cognitiveComplexity } = computeComplexity(tree.topNode);\n const functions = collectFunctionComplexity(tree, content);\n const rawCallEdges = category === \"test\" ? [] : collectRawCallEdges(tree, content);\n\n return {\n imports,\n exports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n rawCallEdges,\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\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// ─── call-edge extraction ──────────────────────────────────────────────────────\n\n/**\n * @description Builds a map from each name bound by a `from <module> import <name> [as alias]`\n * statement to that module's raw specifier (mirroring the specifier computation in\n * `extractFromImport`, including alias resolution and relative-import prefixing). Bare\n * `import <module>` statements are not included: unqualified calls can't tell which bound\n * module a member access like `module.func()` belongs to, so — matching the TS parser's\n * documented exclusion of \"calls through chained member access\" — only directly named imports\n * are tracked as callable symbols.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {Map<string, string>} Local (possibly aliased) name → raw import specifier.\n */\nfunction buildImportSymbolMap(tree: Tree, content: string): Map<string, string> {\n const symbolMap = new Map<string, string>();\n const cursor = tree.cursor();\n\n do {\n if (cursor.name !== \"ImportStatement\") continue;\n const node = cursor.node;\n const fromKw = node.firstChild;\n if (fromKw?.type.name !== \"from\") continue;\n\n let importKw: SyntaxNode | null = fromKw.nextSibling;\n while (importKw && importKw.type.name !== \"import\") importKw = importKw.nextSibling;\n if (!importKw) continue;\n\n const rawModule = content.slice(fromKw.to, importKw.from).trim();\n let dotCount = 0;\n while (dotCount < rawModule.length && rawModule[dotCount] === \".\") dotCount++;\n const modulePart = rawModule.slice(dotCount);\n const prefix = dotCount <= 1 ? \"./\" : \"../\".repeat(dotCount - 1);\n\n let child: SyntaxNode | null = importKw.nextSibling;\n while (child) {\n if (child.type.name === \"VariableName\") {\n const importedName = content.slice(child.from, child.to);\n let localName = importedName;\n if (child.nextSibling?.type.name === \"as\") {\n const aliasNode = child.nextSibling.nextSibling;\n if (aliasNode?.type.name === \"VariableName\") {\n localName = content.slice(aliasNode.from, aliasNode.to);\n child = aliasNode;\n }\n }\n const specifier =\n dotCount === 0\n ? rawModule\n : modulePart\n ? prefix + modulePart.replace(/\\./g, \"/\")\n : prefix + importedName;\n symbolMap.set(localName, specifier);\n }\n child = child.nextSibling;\n }\n } while (cursor.next());\n\n return symbolMap;\n}\n\n/**\n * @description Walks every top-level `FunctionDefinition` and every method directly inside a\n * `ClassDefinition`'s body, recording a `RawCallEdge` for each bare call (`func(...)`) whose\n * callee resolves to a name bound by a `from <module> import <name>` statement. Methods are\n * qualified as `ClassName.methodName`, mirroring the TS parser's convention.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {RawCallEdge[]} One edge per call to a known imported symbol.\n */\nfunction collectRawCallEdges(tree: Tree, content: string): RawCallEdge[] {\n const importSymbols = buildImportSymbolMap(tree, content);\n const edges: RawCallEdge[] = [];\n\n function walkBody(node: SyntaxNode, callerName: string): void {\n if (node.type.name === \"CallExpression\" && node.firstChild?.type.name === \"VariableName\") {\n const calleeNode = node.firstChild;\n const calleeName = content.slice(calleeNode.from, calleeNode.to);\n const toSpecifier = importSymbols.get(calleeName);\n if (toSpecifier) edges.push({ from: callerName, to: calleeName, toSpecifier });\n }\n let child = node.firstChild;\n while (child) {\n walkBody(child, callerName);\n child = child.nextSibling;\n }\n }\n\n function walkChildren(node: SyntaxNode): void {\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n function walk(node: SyntaxNode): void {\n if (node.type.name === \"ClassDefinition\") {\n const classNameNode = node.getChild(\"VariableName\");\n const className = classNameNode\n ? content.slice(classNameNode.from, classNameNode.to)\n : undefined;\n const body = node.getChild(\"Body\");\n if (body) {\n let child = body.firstChild;\n while (child) {\n if (child.type.name === \"FunctionDefinition\") {\n const fnNameNode = child.getChild(\"VariableName\");\n const fnName = fnNameNode ? content.slice(fnNameNode.from, fnNameNode.to) : undefined;\n if (fnName) walkBody(child, className ? `${className}.${fnName}` : fnName);\n } else {\n walk(child);\n }\n child = child.nextSibling;\n }\n }\n return;\n }\n\n if (node.type.name === \"FunctionDefinition\") {\n const nameNode = node.getChild(\"VariableName\");\n if (nameNode) walkBody(node, content.slice(nameNode.from, nameNode.to));\n return;\n }\n\n walkChildren(node);\n }\n\n walk(tree.topNode);\n return edges;\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","/** Computes McCabe cyclomatic complexity and cognitive complexity for Python source, mirroring\n * the TypeScript algorithm in ../complexity.ts but walking the @lezer/python SyntaxNode tree.\n * Python's grammar represents `if`/`elif`/`else` and `try`/`except`/`else`/`finally` as flat\n * sibling sequences within a single node, unlike TS/Go's nested representation, so the\n * branch-chain walking logic differs from ../complexity.ts and ./go.ts even though the overall\n * scoring model (decision points, nesting-aware cognitive penalty) is the same. */\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport type { FunctionComplexity } from \"../../types/node\";\nimport { childrenOf, lineAt } from \"./lezer-utils\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for a Python AST node: every independent\n * decision point counts (base 1) — each `if`/`elif` branch, `for`, `while`, each `except`\n * clause, ternary (`ConditionalExpression`), and each `and`/`or` operator.\n * @param {SyntaxNode} rootNode - The AST root node to analyse — the whole file's top node for\n * file-level totals, or a `FunctionDefinition`/`LambdaExpression` node to score it alone.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: SyntaxNode): number {\n let complexity = 1;\n\n function walk(node: SyntaxNode): void {\n switch (node.type.name) {\n case \"IfStatement\": {\n complexity += childrenOf(node).filter(\n (k) => k.type.name === \"if\" || k.type.name === \"elif\",\n ).length;\n break;\n }\n case \"TryStatement\": {\n complexity += childrenOf(node).filter((k) => k.type.name === \"except\").length;\n break;\n }\n case \"ForStatement\":\n case \"WhileStatement\":\n case \"ConditionalExpression\":\n complexity++;\n break;\n case \"and\":\n case \"or\":\n complexity++;\n break;\n }\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for a Python\n * AST node, tracking how hard the code is to read by adding a nesting penalty. Since Python's\n * `if`/`elif`/`else` chain is one flat `IfStatement` node (not nested, unlike TS/Go), this walks\n * its direct children in groups: each fresh `if` adds `1 + depth` and nests its body; each\n * `elif` adds a flat +1 at the same depth as the original `if`; a bare `else` adds a flat +1 and\n * nests its body. `for`/`while` add `1 + depth` and nest their body. Each `except` clause adds\n * `1 + depth` without increasing nesting for its own body (mirroring how the TS parser scores\n * `catch`). Ternaries and `and`/`or` each add a flat +1. Nested `def`/`lambda` add `1 + depth`.\n * @param {SyntaxNode} rootNode - The AST root node to analyse (nesting depth resets to 0 here).\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: SyntaxNode): number {\n let cognitive = 0;\n\n function walk(node: SyntaxNode, depth: number): void {\n const name = node.type.name;\n\n if (name === \"IfStatement\") {\n const kids = childrenOf(node);\n let branchIndex = 0;\n let i = 0;\n while (i < kids.length) {\n const kw = kids[i];\n if (kw?.type.name === \"if\" || kw?.type.name === \"elif\") {\n const isElseIf = branchIndex > 0;\n cognitive += isElseIf ? 1 : 1 + depth;\n const bodyDepth = isElseIf ? depth : depth + 1;\n const cond = kids[i + 1];\n const body = kids[i + 2];\n if (cond) walk(cond, bodyDepth);\n if (body) walk(body, bodyDepth);\n branchIndex++;\n i += 3;\n } else if (kw?.type.name === \"else\") {\n cognitive += 1;\n const body = kids[i + 1];\n if (body) walk(body, depth + 1);\n i += 2;\n } else {\n i++;\n }\n }\n return;\n }\n\n if (name === \"TryStatement\") {\n const kids = childrenOf(node);\n let i = 0;\n while (i < kids.length) {\n const kw = kids[i];\n if (kw?.type.name === \"except\") {\n cognitive += 1 + depth;\n i++;\n while (i < kids.length && kids[i]?.type.name !== \"Body\") {\n walk(kids[i] as SyntaxNode, depth);\n i++;\n }\n if (i < kids.length) {\n walk(kids[i] as SyntaxNode, depth);\n i++;\n }\n } else if (kw?.type.name === \"Body\") {\n walk(kw, depth);\n i++;\n } else {\n i++;\n }\n }\n return;\n }\n\n if (name === \"ForStatement\" || name === \"WhileStatement\") {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1);\n child = child.nextSibling;\n }\n return;\n }\n\n if (name === \"ConditionalExpression\" || name === \"and\" || name === \"or\") {\n cognitive += 1;\n }\n\n const isNestedFunction =\n depth > 0 && (name === \"FunctionDefinition\" || name === \"LambdaExpression\");\n if (isNestedFunction) {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1);\n child = child.nextSibling;\n }\n return;\n }\n\n let child = node.firstChild;\n while (child) {\n walk(child, depth);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode, 0);\n return cognitive;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and cognitive complexity for a Python\n * AST node by composing `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {SyntaxNode} node - The AST root node to analyse.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores.\n */\nexport function computeComplexity(node: SyntaxNode): {\n complexity: number;\n cognitiveComplexity: number;\n} {\n return {\n complexity: computeCyclomaticComplexity(node),\n cognitiveComplexity: computeCognitiveComplexity(node),\n };\n}\n\n/**\n * @description Walks the entire Python source tree and records complexity for every named\n * `def`: top-level functions, and class methods qualified as `ClassName.methodName` (mirroring\n * the TS parser's convention). Functions nested inside another function or method are recorded\n * with their own bare name, unqualified — matching the TS parser, which never prefixes plain\n * nested function declarations with an enclosing class name either.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {FunctionComplexity[]} Per-function complexity entries, in traversal order.\n */\nexport function collectFunctionComplexity(tree: Tree, content: string): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n\n function recordFunction(node: SyntaxNode, name: string): void {\n const { complexity, cognitiveComplexity } = computeComplexity(node);\n results.push({ name, line: lineAt(content, node.from), complexity, cognitiveComplexity });\n }\n\n function walkChildren(node: SyntaxNode): void {\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n function walk(node: SyntaxNode): void {\n if (node.type.name === \"ClassDefinition\") {\n const classNameNode = node.getChild(\"VariableName\");\n const className = classNameNode\n ? content.slice(classNameNode.from, classNameNode.to)\n : undefined;\n const body = node.getChild(\"Body\");\n if (body) {\n let child = body.firstChild;\n while (child) {\n if (child.type.name === \"FunctionDefinition\") {\n const fnNameNode = child.getChild(\"VariableName\");\n const fnName = fnNameNode ? content.slice(fnNameNode.from, fnNameNode.to) : undefined;\n if (fnName) recordFunction(child, className ? `${className}.${fnName}` : fnName);\n walkChildren(child);\n } else {\n walk(child);\n }\n child = child.nextSibling;\n }\n }\n return;\n }\n\n if (node.type.name === \"FunctionDefinition\") {\n const nameNode = node.getChild(\"VariableName\");\n if (nameNode) recordFunction(node, content.slice(nameNode.from, nameNode.to));\n walkChildren(node);\n return;\n }\n\n walkChildren(node);\n }\n\n walk(tree.topNode);\n return results;\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, stmt.name.text, 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 {string} className - The class's name, pre-extracted by the caller (which already\n * narrowed `classDecl.name` to non-null before calling in).\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 className: string,\n importSymbolMap: Map<string, string>,\n edges: RawCallEdge[],\n): void {\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 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\n * standard shape. SCSS and Less populate `exports`/`tags` from their root-level variable/mixin/function\n * declarations (see `parseScssContent`, `parseLessContent`); CSS and Stylus have no equivalent module\n * surface today, so they always report empty `exports`/`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, exports/tags (SCSS/Less only), 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, exports, tags } = parseScssContent(content, filePath);\n return { imports, exports, tags, category: detectCssBarrel(root, imports) };\n }\n\n if (fileType === \"less\") {\n const { imports, root, exports, tags } = 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 { parseMarkdown } from \"./parser/lang/markdown\";\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 [\"markdown\", parseMarkdown],\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 *resolved* import edges and appends a structured `import`-kind tag\n * for every third-party library found. Scoped packages (`@scope/pkg/deep`) are normalised to their\n * two-segment name before deduplication. Relies on `isExternal` (set by the resolver) rather than\n * specifier syntax, since some languages — notably Sass/Less/Stylus — use bare specifiers\n * (`@import 'variables'`) to mean a local sibling file, not a package.\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.isExternal) {\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\n/**\n * @description Links markdown docs to the code files they reference and flags likely-stale docs.\n * For each markdown node's resolved local imports, records the doc on the target's `documentedBy`\n * (any target, so \"what docs reference this file?\" stays accurate regardless of category), and —\n * when the target's last commit is newer than the doc's own — records the target on the doc's\n * `staleFor`. The staleness check is scoped to `category: \"logic\"` targets only: barrels (e.g.\n * `index.ts`), config files, and non-code files (package.json, lockfiles, other markdown) churn\n * constantly for reasons unrelated to documented behavior — comparing against them made every doc\n * in a repo look perpetually stale. This is still a commit-recency heuristic, not a content diff:\n * a doc can go stale without any timestamp gap (e.g. a behavior change bundled into the same\n * commit as its docs update), and a logic file touched only for formatting will still flag any\n * doc referencing it. Treat `staleFor` as \"worth a look\", not \"definitely wrong\".\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichDocDrift(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.type !== \"markdown\") 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 || target === node) continue;\n\n target.documentedBy ??= [];\n if (!target.documentedBy.includes(node.path)) target.documentedBy.push(node.path);\n\n if (target.category !== \"logic\") continue;\n if ((target.lastCommitAt ?? 0) > (node.lastCommitAt ?? 0)) {\n node.staleFor ??= [];\n if (!node.staleFor.includes(target.path)) node.staleFor.push(target.path);\n }\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 MarkdownLangResolver,\n PythonLangResolver,\n type ResolvedImport,\n StyleLangResolver,\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 * Explicit path-alias map (same shape as tsconfig's `compilerOptions.paths`), e.g.\n * `{ \"@app/*\": [\"src/app/*\"] }`. Checked before `tsconfig.json`-derived aliases, so entries\n * here take precedence — useful for JS-only projects without a tsconfig, Vite/webpack\n * alias configs, or overriding what a tsconfig declares. Substitution paths are resolved\n * relative to `rootDir`.\n */\n pathAliases?: Record<string, string[]> | undefined;\n /**\n * Language-specific resolvers that handle bare (non-relative) specifiers before\n * falling through to the external-module default. Defaults to Python, Lua, Go, and style 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 pathAliases: Record<string, string[]> | undefined;\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.pathAliases = options.pathAliases;\n this.langResolvers = options.langResolvers ?? [\n new PythonLangResolver(),\n new LuaLangResolver(),\n new GoLangResolver(),\n new StyleLangResolver(),\n new MarkdownLangResolver(),\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 \".md\",\n \".mdx\",\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 Matches the specifier against the explicit `pathAliases` config option\n * (if set), then falls back to `tsconfig.json`-derived `compilerOptions.paths` aliases,\n * trying each substitution 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 if (this.pathAliases) {\n for (const alias in this.pathAliases) {\n const match = this.matchAliasPattern(alias, specifier);\n const substitutions = this.pathAliases[alias];\n if (match && substitutions) {\n const resolved = this.tryAliasSubstitutions(substitutions, match[1] || \"\");\n if (resolved) return resolved;\n }\n }\n }\n\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 Markdown: resolves bare doc-style paths (e.g. `src/graph/builder.ts`), the project-root-relative convention used throughout mokosh's own docs, since those don't start with `.` or `/` and would otherwise fall through to \"external\". */\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\nexport class MarkdownLangResolver implements LangResolver {\n extensions = [\".md\", \".mdx\"];\n\n /**\n * @description Treats a bare specifier as relative to the project root rather than the\n * markdown file's own directory, by probing it against a dummy file inside `rootDir`.\n * @param {string} _currentFile - Absolute path of the markdown file (unused; resolution is root-relative).\n * @param {string} specifier - The raw bare specifier extracted from a link or code span, e.g. `\"src/graph/builder.ts\"`.\n * @param {string} rootDir - Absolute project root directory.\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport[] | null} The resolved local file, or `null` to fall through to external.\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 resolved = resolveLocal(path.join(rootDir, \"_dummy\"), `./${specifier}`);\n return resolved ? [resolved] : 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","/** Language resolver for style dialects: resolves bare `@import`/`@require`/`@use` specifiers relative to the importing file. */\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\nconst EXTERNAL_PREFIXES = [\"~\", \"http://\", \"https://\", \"//\", \"data:\", \"sass:\"];\n\n/**\n * @description Resolves bare style import specifiers (e.g. `@import 'variables'` in Sass/Less/Stylus,\n * which by convention means \"same directory\", unlike JS bare specifiers which mean \"node_modules\").\n * Also tries the Sass/SCSS partial-file convention (`variables` → `_variables.scss`).\n */\nexport class StyleLangResolver implements LangResolver {\n extensions = [\".css\", \".scss\", \".sass\", \".less\", \".styl\"];\n\n /**\n * @description Treats a bare specifier as relative to the importing file's directory, then\n * falls back to the Sass/SCSS underscore-partial convention before giving up.\n * @param {string} currentFile - Absolute path of the importing style file.\n * @param {string} specifier - The raw bare specifier from source, e.g. `\"variables\"`.\n * @param {string} _rootDir - Absolute project root (unused; resolution is directory-relative).\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport[] | null} The resolved local file, or `null` to fall through to external.\n */\n resolve(\n currentFile: string,\n specifier: string,\n _rootDir: string,\n resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n if (EXTERNAL_PREFIXES.some((prefix) => specifier.startsWith(prefix))) return null;\n\n const resolved = resolveLocal(currentFile, specifier);\n if (resolved) return [resolved];\n\n const dir = path.dirname(specifier);\n const base = path.basename(specifier);\n const partialSpecifier = dir === \".\" ? `_${base}` : `${dir}/_${base}`;\n const partialResolved = resolveLocal(currentFile, partialSpecifier);\n if (partialResolved) return [partialResolved];\n\n return null;\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, configToGraphOptions, 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 type { ParallelParsingOption } from \"./graph/builder\";\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 type DuplicateFamily,\n type DuplicateGroup,\n type DuplicateOccurrence,\n type FindDuplicatesOptions,\n findDuplicates,\n} from \"./graph/duplication\";\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\";\nexport {\n CALL_EDGE_TYPES,\n EXPORT_TRACKING_TYPES,\n getLanguageCoverage,\n IMPORT_SYMBOL_TYPES,\n type LanguageCoverage,\n} from \"./graph/language-support\";\n// Core graph classes\nexport { Graph } from \"./graph/model\";\nexport {\n type CallerEntry as GraphCallerEntry,\n type ComplexFunctionEntry,\n type FindComplexFunctionsOptions,\n findComplexFunctions,\n type GetAffectedOptions,\n type GetCallersOptions,\n getAffected,\n getCallers,\n getDependencies,\n getDependents,\n getNodeMeta,\n hasCoverageData,\n type NodeMeta,\n type PathWithSymbols,\n type SlimNode,\n type SlimSerializedGraph,\n slimSerialize,\n summarizeWorkspacePackages,\n type WorkspacePackageSummary,\n type WorkspacePackagesSummary,\n} from \"./graph/queries\";\nexport { buildResponsibilityGraph } from \"./graph/responsibility\";\nexport type {\n ModuleResponsibility,\n ModuleRole,\n ResponsibilityGraph,\n} from \"./graph/responsibility/types\";\nexport {\n findSymbol,\n type SymbolCaller,\n type SymbolImporter,\n type SymbolMatch,\n type SymbolPrecision,\n} from \"./graph/symbol\";\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 {\n DefaultResolver,\n detectMonorepo,\n type Graph,\n GraphBuilder,\n type ParallelParsingOption,\n WorkspaceGraph,\n} 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; `parallelParsing` controls worker-pool offloading of file parsing (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution (see `MokoshConfig.pathAliases`).\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: {\n silent?: boolean;\n gitStats?: boolean;\n coverageMap?: Map<string, number>;\n parallelParsing?: ParallelParsingOption | undefined;\n pathAliases?: Record<string, string[]> | undefined;\n } = {},\n): Promise<Graph> {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`Processed ${count} files...\\r`);\n };\n const abs = path.resolve(rootDir);\n const builder = new GraphBuilder(\n abs,\n previousGraph,\n options.pathAliases\n ? new DefaultResolver(abs, { pathAliases: options.pathAliases })\n : undefined,\n progressCallback,\n options.gitStats ?? false,\n options.coverageMap ?? new Map(),\n options.parallelParsing ?? true,\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; `parallelParsing` controls worker-pool offloading of file parsing per package (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution for every package (see `MokoshConfig.pathAliases`).\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: {\n packages?: string[];\n silent?: boolean;\n gitStats?: boolean;\n parallelParsing?: ParallelParsingOption | undefined;\n pathAliases?: Record<string, string[]> | undefined;\n } = {},\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, {\n workspaceMap,\n tsconfigSearchPaths: [pkg.root, abs],\n pathAliases: options.pathAliases,\n }),\n progressCallback,\n options.gitStats ?? false,\n new Map(),\n options.parallelParsing ?? true,\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 checkDocDrift: boolean;\n findUncovered: boolean;\n listTags: 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 findSymbol: boolean;\n functionName: string | undefined;\n apiSurface: boolean;\n applyTags: boolean;\n dryRun: boolean;\n initSkill: boolean;\n initConfig: boolean;\n force: boolean;\n dependencies: boolean;\n dependents: boolean;\n affected: boolean;\n testsOnly: boolean;\n changedSymbols: string[] | undefined;\n cached: boolean;\n depth: number | undefined;\n withMeta: boolean;\n withEdgeDetail: boolean;\n findComplexFunctions: boolean;\n metric: \"cognitiveComplexity\" | \"complexity\" | undefined;\n complexityThreshold: number | undefined;\n limit: number | undefined;\n findDuplicates: boolean;\n minDuplicateLines: number | undefined;\n workspacePackages: boolean;\n workspaceAffected: boolean;\n clearCache: boolean;\n slim: boolean;\n watch: 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 a raw numeric CLI value, returning `undefined` for missing/empty input.\n * @param {string | undefined} raw - The raw string value from `nodeParseArgs`, if provided.\n * @returns {number | undefined} The parsed integer, or `undefined` if `raw` is falsy.\n */\nfunction parseOptionalInt(raw: string | undefined): number | undefined {\n return raw ? parseInt(raw, 10) : undefined;\n}\n\n/**\n * @description Parses a raw comma-separated CLI value into a trimmed string array.\n * @param {string | undefined} raw - The raw comma-separated string from `nodeParseArgs`, if provided.\n * @returns {string[] | undefined} The trimmed parts, or `undefined` if `raw` is falsy.\n */\nfunction parseCsv(raw: string | undefined): string[] | undefined {\n return raw ? raw.split(\",\").map((part) => part.trim()) : undefined;\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 const 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 \"check-doc-drift\": { type: \"boolean\" },\n \"find-uncovered\": { type: \"boolean\" },\n \"list-tags\": { 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 \"find-symbol\": { type: \"boolean\" },\n \"api-surface\": { type: \"boolean\" },\n \"apply-tags\": { type: \"boolean\" },\n \"dry-run\": { type: \"boolean\" },\n \"init-skill\": { type: \"boolean\" },\n \"init-config\": { type: \"boolean\" },\n force: { type: \"boolean\" },\n dependencies: { type: \"boolean\" },\n dependents: { type: \"boolean\" },\n affected: { type: \"boolean\" },\n \"tests-only\": { type: \"boolean\" },\n \"changed-symbols\": { type: \"string\" },\n cached: { type: \"boolean\" },\n depth: { type: \"string\" },\n \"with-meta\": { type: \"boolean\" },\n \"with-edge-detail\": { type: \"boolean\" },\n \"find-complex-functions\": { type: \"boolean\" },\n metric: { type: \"string\" },\n \"complexity-threshold\": { type: \"string\" },\n limit: { type: \"string\" },\n \"find-duplicates\": { type: \"boolean\" },\n \"min-duplicate-lines\": { type: \"string\" },\n \"workspace-packages\": { type: \"boolean\" },\n \"workspace-affected\": { type: \"boolean\" },\n \"clear-cache\": { type: \"boolean\" },\n slim: { type: \"boolean\" },\n watch: { 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 === undefined) continue;\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 cacheValue = values.cache;\n const configValue = values.config;\n const metricRaw = values.metric;\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: parseCsv(values.paths),\n featureThreshold: parseOptionalInt(values[\"feature-threshold\"]),\n minOutDegree: parseOptionalInt(values[\"min-out-degree\"]),\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 checkDocDrift: values[\"check-doc-drift\"] ?? false,\n findUncovered: values[\"find-uncovered\"] ?? false,\n listTags: values[\"list-tags\"] ?? 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 findSymbol: values[\"find-symbol\"] ?? false,\n apiSurface: values[\"api-surface\"] ?? false,\n applyTags: values[\"apply-tags\"] ?? false,\n dryRun: values[\"dry-run\"] ?? false,\n initSkill: values[\"init-skill\"] ?? false,\n initConfig: values[\"init-config\"] ?? false,\n force: values.force ?? false,\n dependencies: values.dependencies ?? false,\n dependents: values.dependents ?? false,\n affected: values.affected ?? false,\n testsOnly: values[\"tests-only\"] ?? false,\n changedSymbols: parseCsv(values[\"changed-symbols\"]),\n cached: values.cached ?? false,\n depth: parseOptionalInt(values.depth),\n withMeta: values[\"with-meta\"] ?? false,\n withEdgeDetail: values[\"with-edge-detail\"] ?? false,\n findComplexFunctions: values[\"find-complex-functions\"] ?? false,\n metric:\n metricRaw === \"complexity\" || metricRaw === \"cognitiveComplexity\" ? metricRaw : undefined,\n complexityThreshold: parseOptionalInt(values[\"complexity-threshold\"]),\n limit: parseOptionalInt(values.limit),\n findDuplicates: values[\"find-duplicates\"] ?? false,\n minDuplicateLines: parseOptionalInt(values[\"min-duplicate-lines\"]),\n workspacePackages: values[\"workspace-packages\"] ?? false,\n workspaceAffected: values[\"workspace-affected\"] ?? false,\n clearCache: values[\"clear-cache\"] ?? false,\n slim: values.slim ?? false,\n watch: values.watch ?? false,\n entryPoints: positionals,\n };\n}\n","export const DEFAULT_CACHE_DIR = \"mokosh-cache\";\nexport const DEFAULT_CACHE_FILE = \"graph.json\";\n","/** CLI command: prints all files transitively affected if a given file changes, mirroring the MCP get_affected tool. */\nimport { buildChangeImpactCache, getAffected, getNodeMeta, queryChangeImpact } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints every file transitively affected if `--file` changes (full incoming\n * traversal). Set `--tests-only` to restrict to test/spec files, `--changed-symbols` to\n * restrict propagation to files that import those specific symbols, `--cached` to use\n * a precomputed impact cache instead of a fresh traversal, and `--with-meta` to turn each\n * affected path into `{ path, category, exports }`.\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, testsOnly, changedSymbols, cached, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --affected requires --file <path>\");\n process.exit(1);\n }\n\n const annotate = (paths: string[]) =>\n withMeta ? paths.map((p) => ({ path: p, ...getNodeMeta(graph, p) })) : paths;\n\n if (cached) {\n const impactCache = buildChangeImpactCache(graph);\n const allAffected = queryChangeImpact(impactCache, file);\n const affected = testsOnly\n ? allAffected.filter((filePath) => graph.nodes.get(filePath)?.category === \"test\")\n : allAffected;\n const result = annotate(affected);\n console.log(JSON.stringify({ file, affected: result, count: result.length }, null, 2));\n return;\n }\n\n const affected = getAffected(graph, file, { testsOnly, changedSymbols });\n const result = annotate(affected);\n console.log(JSON.stringify({ file, affected: result, count: result.length }, null, 2));\n}\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 {\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n proposeAffectedTests,\n} 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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, configToGraphOptions, 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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 { getCallers } from \"../../index\";\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, depth, withEdgeDetail } = ctx;\n\n if (!file) {\n console.error(\"Error: --callers requires --file <path>\");\n process.exit(1);\n }\n\n const callers = getCallers(graph, file, { depth: depth ?? 1, withEdgeDetail });\n\n if (plain) {\n console.log(callers.map((caller) => caller.file).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: flags markdown docs whose referenced files changed more recently than the doc itself. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans markdown nodes for `staleFor` entries (populated by `enrichDocDrift`) and\n * prints each stale doc alongside the files that outpaced it. Exits with code 1 if any are\n * found; otherwise confirms a clean state to stdout. This is a commit-recency heuristic, not\n * a content diff — see `docs/adr-009-markdown-parsing.md` for the known limitations.\n * Requires the graph to have been built with git stats enabled (`--git-stats` or config\n * equivalent); otherwise every doc has no `lastCommitAt` data and nothing is flagged.\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 staleDocs = [...graph.nodes.values()]\n .filter((node) => node.type === \"markdown\" && node.staleFor && node.staleFor.length > 0)\n .map((node) => ({ doc: node.path, staleFor: node.staleFor as string[] }));\n\n if (staleDocs.length > 0) {\n process.stderr.write(`Found ${staleDocs.length} doc(s) that may be out of date:\\n`);\n for (const { doc, staleFor } of staleDocs) {\n process.stderr.write(` ${doc} — references changed more recently: ${staleFor.join(\", \")}\\n`);\n }\n process.exit(1);\n }\n console.log(\"No doc drift detected.\");\n}\n","/** CLI command: deletes the disk graph cache file, mirroring the MCP clear_cache tool. */\nimport fs from \"node:fs\";\n\n/**\n * @description Deletes the resolved graph cache file, if present, so the next run rebuilds from scratch.\n * @param {string} cachePath - Absolute path to the cache file (resolved from `--cache` or the default).\n */\nexport function runClearCache(cachePath: string): void {\n if (fs.existsSync(cachePath)) {\n fs.unlinkSync(cachePath);\n console.log(`Cache cleared: ${cachePath}`);\n } else {\n console.log(`No cache file present at ${cachePath}`);\n }\n}\n","/** CLI command: prints files that a given file imports (outgoing traversal), mirroring the MCP get_dependencies tool. */\nimport { getDependencies, getNodeMeta } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints files reachable from `--file` via outgoing import edges, up to `--depth` hops.\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, depth, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --dependencies requires --file <path>\");\n process.exit(1);\n }\n\n const deps = getDependencies(graph, file, depth ?? 1);\n const dependencies = withMeta\n ? deps.map((dep) => ({ ...dep, ...getNodeMeta(graph, dep.path) }))\n : deps;\n console.log(JSON.stringify({ file, dependencies }, null, 2));\n}\n","/** CLI command: prints files that directly import a given file (one-hop incoming), mirroring the MCP get_dependents tool. */\nimport { getDependents, getNodeMeta } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints files that directly import `--file`.\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, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --dependents requires --file <path>\");\n process.exit(1);\n }\n\n const deps = getDependents(graph, file);\n const dependents = withMeta\n ? deps.map((dep) => ({ ...dep, ...getNodeMeta(graph, dep.path) }))\n : deps;\n console.log(JSON.stringify({ file, dependents }, null, 2));\n}\n","/** CLI command: detects feature-hub files and prints them as JSON. */\nimport {\n configToGraphOptions,\n createImportMap,\n detectFeatures,\n getAllProjectFiles,\n} 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, rawConfig } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph, configToGraphOptions(rawConfig));\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 {\n buildFeatureGraph,\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n} 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, rawConfig } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph, configToGraphOptions(rawConfig));\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 functions/methods at or above a complexity threshold, mirroring the MCP find_complex_functions tool. */\nimport { findComplexFunctions } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans every file's per-function complexity breakdown and prints functions/methods\n * at or above `--complexity-threshold`, sorted worst-first. Populated for TypeScript/JavaScript,\n * Go, and Python.\n * @param {CommandContext} ctx - Shared command context; `ctx.metric`, `ctx.complexityThreshold`, and `ctx.limit` tune the scan.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, metric, complexityThreshold, limit } = ctx;\n const threshold = complexityThreshold ?? 10;\n const functions = findComplexFunctions(graph, { metric, threshold, limit });\n console.log(\n JSON.stringify(\n { metric: metric ?? \"cognitiveComplexity\", threshold, functions, count: functions.length },\n null,\n 2,\n ),\n );\n}\n","/** CLI command: lists duplicated code blocks across the project, mirroring the MCP find_duplicates tool. */\nimport { DEFAULT_IGNORE_DIRS, findDuplicates } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans every file in the graph for cross-file (and within-file) duplicated code\n * and prints the resulting blocks, largest-first. Works for every language mokosh parses —\n * the underlying detector is token-based, not AST-based, so it doesn't depend on per-language\n * parser support. Lock files and files under an ignored directory (project config's\n * `ignoreDirs`, merged with the defaults) are excluded even when the graph itself contains\n * them — `graph.nodes` isn't ignore-rule-filtered for files reached via a resolved reference\n * rather than the initial FS walk.\n * @param {CommandContext} ctx - Shared command context; `ctx.rootDir`, `ctx.scanOptions`,\n * `ctx.minDuplicateLines`, and `ctx.limit` tune the scan.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, rootDir, scanOptions, minDuplicateLines, limit } = ctx;\n const minLines = minDuplicateLines ?? 6;\n const ignoreDirs = [\n ...(scanOptions.ignoreDirs ?? DEFAULT_IGNORE_DIRS),\n ...(scanOptions.additionalIgnoreDirs ?? []),\n ];\n const groups = await findDuplicates(graph, rootDir, { minLines, limit, ignoreDirs });\n console.log(JSON.stringify({ minLines, groups, count: groups.length }, null, 2));\n}\n","/** CLI command: finds every file that exports a symbol by name. */\nimport { findSymbol } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Finds every file that exports `functionName` (reused as the symbol name),\n * with the best available usage info per match — call-edge callers for TS/JS, named-import\n * tracking for Python, whole-file dependents otherwise.\n * @param {CommandContext} ctx - Shared command context; `ctx.functionName` must be set via `--function`.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, functionName, plain } = ctx;\n\n if (!functionName) {\n console.error(\"Error: --find-symbol requires --function <name>\");\n process.exit(1);\n }\n\n const matches = findSymbol(graph, functionName);\n\n if (plain) {\n console.log(matches.map((match) => match.path).join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ name: functionName, matches, count: matches.length }, null, 2));\n }\n}\n","/** CLI command: lists non-test files below the configured coverage threshold. */\nimport { hasCoverageData } from \"../../index\";\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 * if no coverage data was ever loaded, prints an error instead of misreporting every file as uncovered.\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 if (!hasCoverageData(graph)) {\n console.log(\n JSON.stringify(\n {\n error:\n \"No coverage data available. Set coverageReportPath in mokosh.config and rebuild the graph.\",\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n const uncovered = [...graph.nodes.values()]\n .filter((node) => node.category !== \"test\" && node.category !== \"config\")\n .filter((node) => node.coveragePct !== undefined && node.coveragePct < threshold)\n .map((node) => ({ file: node.path, coveragePct: node.coveragePct as number }));\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 {\n filterGraph,\n Graph,\n getLanguageCoverage,\n MermaidExporter,\n parseQuery,\n slimSerialize,\n} 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 * Pass `--slim` for a compact response (export names, meaningful tags, flat importsFiles list) —\n * matching the MCP `query` tool's default shape; the CLI defaults to the full shape (`--slim` false)\n * to preserve existing script compatibility.\n * @param {CommandContext} ctx - Command context carrying the built graph, an optional query string, and the mermaid/slim output flags.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, queryStr, mermaidOutput, slim } = 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 return;\n }\n\n const cycles = graph.findCycles();\n if (cycles.length > 0) {\n serialized.cycles = cycles;\n }\n const languageCoverage = getLanguageCoverage(graph);\n\n if (slim) {\n console.log(JSON.stringify({ ...slimSerialize(serialized), languageCoverage }, null, 2));\n } else {\n console.log(JSON.stringify({ ...serialized, languageCoverage }, null, 2));\n }\n}\n","/** CLI command: scaffolds a starter mokosh.config.js into the current project. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findTemplateDir } from \"./template-dir\";\n\n/**\n * @description Copies the bundled `mokosh.config.js` template into the project root, so a\n * user gets a commented starter config instead of hand-writing one from docs. Existing\n * files are left untouched unless `force` is passed. The MCP server only reads\n * `mokosh.config.json` (JS execution is disabled there), so this scaffolds a `.js` file\n * aimed at CLI/programmatic use where comments and factory functions are supported.\n * @param {boolean} force - When true, overwrite the file if it already exists.\n * @param {string} cwd - Project directory to scaffold into (default: `process.cwd()`).\n */\nexport function runInitConfig(force: boolean, cwd: string = process.cwd()): void {\n const templateDir = findTemplateDir(__dirname, [\"config\"], \"mokosh.config.js\");\n const src = path.join(templateDir, \"mokosh.config.js\");\n const dest = path.join(cwd, \"mokosh.config.js\");\n\n if (fs.existsSync(dest) && !force) {\n console.log(\"skipped mokosh.config.js (already exists, use --force to overwrite)\");\n return;\n }\n\n fs.copyFileSync(src, dest);\n console.log(\"wrote mokosh.config.js\");\n}\n","/** Shared helper for locating bundled `templates/` subdirectories from a compiled or source layout. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * @description Locates a bundled `templates/<subpath>` directory by walking up from `startDir`,\n * checking for `markerFile` at each level. Templates live one level above the compiled\n * `dist/cli.js` in the published package, but several levels above `src/cli/commands/` when\n * running against source (tests, ts-node) — walking up avoids hardcoding either layout.\n * @param {string} startDir - Directory to start searching from (typically `__dirname`).\n * @param {string[]} subpathSegments - Path segments under `templates/` to locate, e.g. `[\"skill\"]`.\n * @param {string} markerFile - Filename within the target directory used to confirm it was found.\n * @returns {string} Absolute path to the located `templates/<subpath>` directory.\n */\nexport function findTemplateDir(\n startDir: string,\n subpathSegments: string[],\n markerFile: string,\n): string {\n let dir = startDir;\n for (let i = 0; i < 6; i++) {\n const candidate = path.join(dir, \"templates\", ...subpathSegments);\n if (fs.existsSync(path.join(candidate, markerFile))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(`Could not locate mokosh templates/${subpathSegments.join(\"/\")}`);\n}\n","/** CLI command: scaffolds the bundled mokosh Claude Code skill/command into the current project. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findTemplateDir } from \"./template-dir\";\n\ninterface Target {\n templateFile: string;\n destPath: string[];\n}\n\nconst TARGETS: Target[] = [\n { templateFile: \"SKILL.md\", destPath: [\".claude\", \"skills\", \"mokosh\", \"SKILL.md\"] },\n { templateFile: \"mokosh.md\", destPath: [\".claude\", \"commands\", \"mokosh.md\"] },\n];\n\n/**\n * @description Copies the bundled mokosh skill (`SKILL.md`) and slash command (`mokosh.md`)\n * templates into the target project's `.claude/` directory, so a downstream project's\n * Claude Code picks up guidance on driving mokosh via MCP or CLI. Existing files are left\n * untouched unless `force` is passed.\n * @param {boolean} force - When true, overwrite files that already exist at the destination.\n * @param {string} cwd - Project directory to scaffold into (default: `process.cwd()`).\n */\nexport function runInitSkill(force: boolean, cwd: string = process.cwd()): void {\n const templateDir = findTemplateDir(__dirname, [\"skill\"], \"SKILL.md\");\n\n for (const target of TARGETS) {\n const src = path.join(templateDir, target.templateFile);\n const dest = path.join(cwd, ...target.destPath);\n const relDest = path.join(...target.destPath);\n\n if (fs.existsSync(dest) && !force) {\n console.log(`skipped ${relDest} (already exists, use --force to overwrite)`);\n continue;\n }\n\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.copyFileSync(src, dest);\n console.log(`wrote ${relDest}`);\n }\n}\n","/** CLI command: lists every distinct tag in the graph with its per-node count. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Counts how many nodes carry each distinct tag name across the graph and prints\n * them sorted by count descending. Lets you check what tags exist before filtering with\n * `--query \"tag:<name>\"` — mirrors the MCP `list_tags` tool. Includes all tag kinds, not just\n * the subset kept by `--query`'s slim output.\n * @param {CommandContext} ctx - Shared command context carrying the built graph.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, plain } = ctx;\n\n const counts = new Map<string, number>();\n for (const node of graph.nodes.values()) {\n for (const tag of node.tags) {\n counts.set(tag.name, (counts.get(tag.name) ?? 0) + 1);\n }\n }\n const tags = [...counts.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((a, b) => b.count - a.count);\n\n if (plain) {\n console.log(tags.map((tag) => tag.name).join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ tags, count: tags.length }, 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 {\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n proposeTags,\n} 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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","/** CLI command: cross-package blast-radius analysis, mirroring the MCP get_workspace_affected tool. */\nimport type { WorkspaceGraph } from \"../../index\";\n\n/**\n * @description Prints every file (annotated with its package) that could be affected if `file` changes.\n * @param {WorkspaceGraph} wg - The workspace graph built for the monorepo root.\n * @param {string} file - Monorepo-root-relative path of the changed file.\n */\nexport function runWorkspaceAffected(wg: WorkspaceGraph, file: string): void {\n const affected = wg.getAffectedAcrossPackages(file);\n console.log(JSON.stringify({ file, affected, count: affected.length }, null, 2));\n}\n","/** CLI command: lists monorepo packages and their cross-package dependencies, mirroring the MCP get_workspace_packages tool. */\nimport { summarizeWorkspacePackages, type WorkspaceGraph } from \"../../index\";\n\n/**\n * @description Prints every workspace package detected in a monorepo, with node counts and\n * cross-package dependencies.\n * @param {WorkspaceGraph} wg - The workspace graph built for the monorepo root.\n */\nexport function runWorkspacePackages(wg: WorkspaceGraph): void {\n console.log(JSON.stringify(summarizeWorkspacePackages(wg), null, 2));\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\";\nimport { DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE } from \"./const\";\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 Picks the effective cache path: an explicit CLI override wins, then the\n * config-file value (resolved against rootDir), then the computed default.\n * @param {string} cachePath - The CLI-resolved cache path (already defaulted if unset).\n * @param {string} defaultCachePath - The computed default cache path to compare against.\n * @param {string | undefined} configCachePath - The cache path from mokosh.config.*, if any.\n * @param {string} rootDir - The project root used to resolve a relative configCachePath.\n * @returns {string} The effective absolute cache path.\n */\nfunction resolveCachePath(\n cachePath: string,\n defaultCachePath: string,\n configCachePath: string | undefined,\n rootDir: string,\n): string {\n if (cachePath !== defaultCachePath) return cachePath;\n if (configCachePath) return path.resolve(rootDir, configCachePath);\n return defaultCachePath;\n}\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, DEFAULT_CACHE_DIR), DEFAULT_CACHE_FILE);\n\n const resolvedEntryPoints = entryPoints.length > 0 ? entryPoints : (config.entryPoints ?? []);\n\n const resolvedCachePath = resolveCachePath(\n cachePath,\n defaultCachePath,\n config.cachePath,\n rootDir,\n );\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 { configToGraphOptions, 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 {object} [options] - `silent` suppresses progress output; the rest is the graph-build subset of `MokoshConfig` — build via {@link configToGraphOptions}.\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 options: { silent?: boolean } & ReturnType<typeof configToGraphOptions> = {\n silent: false,\n ...configToGraphOptions(undefined),\n },\n): Promise<Graph> {\n return createImportMap(rootDir, entryPoints, cachedGraph, options);\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 --find-uncovered List non-test files whose coverage is below the threshold (requires coverageReportPath in config)\n --list-tags List every distinct tag in the graph with its per-node count, sorted\n by count descending (use with --plain for a bare name list)\n --callers List files whose exported functions call into --file\n --file <path> Target file for --callers/--dependencies/--dependents/--affected/--workspace-affected\n --check-cycles Check for circular dependencies; exits non-zero if found (CI gate)\n --check-doc-drift Flag markdown docs whose referenced files changed more recently\n than the doc itself; exits non-zero if found (CI gate; requires\n gitStats: true, see docs/adr-009-markdown-parsing.md)\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 --slim Compact node output for default/--query JSON: export names, meaningful\n tags, and a flat importsFiles list instead of full edge metadata\n (default: false — opt-in, unlike the MCP query tool which defaults slim to true)\n --root <dir> Project root directory (default: current directory)\n --init-skill Scaffold the mokosh Claude Code skill/command into .claude/\n --init-config Scaffold a starter mokosh.config.js in the project root\n --force Overwrite existing files (use with --init-skill/--init-config)\n --clear-cache Delete the cache file at --cache and exit\n --help Show help\n\nMCP parity (mirrors the MCP server's tools for use when MCP is unavailable):\n --dependencies List all files that --file imports (transitive; use with --depth)\n --dependents List files that directly import --file (one hop)\n --depth <N> Max traversal depth for --dependencies/--callers (default: 1)\n --affected List all files transitively affected if --file changes\n --tests-only Restrict --affected to test/spec files\n --changed-symbols <a,b,...> Restrict --affected to files that import these specific symbols\n --cached Use a precomputed impact cache for --affected instead of a fresh traversal\n --with-meta Add category/exports per result for --dependencies/--dependents/--affected\n --with-edge-detail Include from/to function names per edge (use with --callers)\n --find-complex-functions List functions/methods at or above a complexity threshold, worst-first\n --metric <name> cognitiveComplexity (default) | complexity — used by --find-complex-functions\n --complexity-threshold <N> Minimum score to include (default: 10, use with --find-complex-functions)\n --limit <N> Max results for --find-complex-functions/--find-duplicates\n --find-duplicates Find duplicated code blocks (token-based, works for every parsed\n language), largest-first. Lock files and files under an ignored\n directory (--config's ignoreDirs, merged with the defaults) are\n always excluded\n --min-duplicate-lines <N> Minimum duplicated block size in lines to report (default: 6,\n use with --find-duplicates)\n --workspace-packages List monorepo packages and their cross-package dependencies\n --workspace-affected Cross-package blast-radius for --file in a monorepo\n --find-symbol Find every file that exports --function <name> by exact name, with\n the best available caller/importer info per match (call-edge\n precision for TS/JS, named-import for Python, file-level otherwise)\n --watch Re-run on file changes. Supported with the default output, --query,\n --callers, --dependencies, --dependents, --affected, --find-uncovered,\n --find-complex-functions, --find-duplicates, --check-cycles,\n --check-doc-drift, and\n --list-tags only.\n\nNotes:\n Add mokosh-cache/ to your .gitignore to avoid committing the cache directory.\n --check-cycles is CLI-only (CI gate via exit code); it has no MCP equivalent by design.\n --check-doc-drift has an MCP equivalent (check_doc_drift) since its result list, not just a\n pass/fail exit code, is useful to an AI assistant deciding which docs to fix.\n --config files may be .js/.cjs and are executed for CLI convenience (allowJs: true); the MCP\n server only loads JSON config (allowJs: false) for security. This is intentional.\n --workspace-packages/--workspace-affected require explicit opt-in (unlike MCP's analyze tool,\n which auto-detects a monorepo when entryPoints is empty); other commands remain single-package.\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 | python | go | css | scss | less |\n stylus | 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\n minCoverage:<pct> Line coverage % >= N. No-data nodes are excluded.\n maxCoverage:<pct> Line coverage % <= N. No-data nodes are treated as 0%.\n Examples: minCoverage:80 maxCoverage:20\n\n minExportUsage:<0-1> Average exportUsageRatio >= N. No-data nodes are excluded.\n maxExportUsage:<0-1> Average exportUsageRatio <= N. No-data nodes treated as 0.\n Example: maxExportUsage:0.2\n\n minComplexity:<N> McCabe cyclomatic complexity >= N. No-data nodes excluded.\n maxComplexity:<N> McCabe cyclomatic complexity <= N. No-data nodes treated as 0.\n Example: minComplexity:15\n\n minCognitiveComplexity:<N> Cognitive complexity >= N. No-data nodes excluded.\n maxCognitiveComplexity:<N> Cognitive complexity <= N. No-data nodes treated as 0.\n Example: minCognitiveComplexity:10\n\n minCommits:<N> commitCount90d >= N (requires gitStats: true). No-data nodes excluded.\n maxCommits:<N> commitCount90d <= N. No-data nodes treated as 0.\n Example: minCommits:5\n\n isDocumented:<bool> true = at least one markdown doc references this file.\n false = undocumented-by-doc files only.\n Example: isDocumented:false\n\n isStale:<bool> true = node is flagged as doc-stale (staleFor non-empty; see\n check-doc-drift). Example: isStale:true\n\n lastAuthor:<value> Exact match on the file's most recent git author. Negate with !.\n Requires gitStats: true.\n Example: lastAuthor:jane\n\nOR LOGIC\n any(key:val|key:val) OR-group: node matches if it satisfies ANY single-key clause inside\n the group. ANDed with every other key in the query string. Each\n clause is a single key:value pair — multi-key AND clauses inside a\n group are not supported.\n Examples: any(category:logic|category:ui)\n path:src,any(tag:auth|tag:payments)\n\nSORTING & LIMITING (applied after all filters)\n sort:<field> Sort results 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 exportUsage — average exportUsageRatio\n complexity — McCabe cyclomatic complexity\n cognitiveComplexity — cognitive complexity\n Default direction is descending; see sortDir. Example: sort:imports\n\n sortDir:<asc|desc> Sort direction, applies to sort. Default: desc.\n Example: sort:size,sortDir:asc\n\n limit:<N> Return at most N results.\n Example: limit:20\n\nOUTPUT SHAPE\n --slim Pass alongside --query (or the default output) for a compact response:\n export names, meaningful tags, and a flat importsFiles path list — no\n edge objects, no mtime/size. Off by default on the CLI; the MCP query\n tool defaults this on.\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","/** Long-running `--watch` mode: re-runs a command whenever a source file changes, debounced. */\nimport fs from \"node:fs\";\nimport { IGNORE_WATCH } from \"../watch-ignore\";\n\n/**\n * @description Runs `rerun` immediately, then again on every debounced batch of file changes under\n * `rootDir` (ignoring `node_modules`/`.git`/`dist`/`build`/`coverage`, matching the MCP session\n * cache's watch behavior). Overlapping triggers collapse into a single trailing re-run. Never\n * returns on its own — the process stays alive until `SIGINT` closes the watcher and exits.\n * @param {string} rootDir - Absolute path of the directory to watch.\n * @param {number} debounceMs - Milliseconds to wait after the last change before re-running.\n * @param {() => Promise<void>} rerun - Rebuilds the graph and re-invokes the command handler.\n */\nexport function watchAndRun(rootDir: string, debounceMs: number, rerun: () => Promise<void>): void {\n let timer: NodeJS.Timeout | null = null;\n let running = false;\n let pending = false;\n\n const trigger = async () => {\n if (running) {\n pending = true;\n return;\n }\n running = true;\n try {\n await rerun();\n } finally {\n running = false;\n if (pending) {\n pending = false;\n void trigger();\n }\n }\n };\n\n void trigger();\n\n let watcher: fs.FSWatcher | null = null;\n try {\n watcher = fs.watch(rootDir, { recursive: true }, (_event, filename) => {\n if (!filename || IGNORE_WATCH.test(filename)) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void trigger();\n }, debounceMs);\n });\n } catch (err) {\n console.error(`Warning: unable to watch ${rootDir}: ${(err as Error).message}`);\n }\n\n process.on(\"SIGINT\", () => {\n if (timer) clearTimeout(timer);\n watcher?.close();\n process.exit(0);\n });\n}\n","/** Shared fs.watch ignore pattern, used by both the MCP session cache and the CLI's --watch mode. */\nexport const IGNORE_WATCH = /(?:^|[/\\\\])(?:node_modules|\\.git|dist|build|coverage)(?:[/\\\\]|$)/;\n","/** CLI entry point: parses arguments, loads configuration and the graph, then dispatches to the appropriate command. */\nimport {\n applyConfig,\n configToGraphOptions,\n createWorkspaceGraph,\n Graph,\n type MokoshConfig,\n} from \"../index\";\nimport type { ParsedArgs } from \"./args\";\nimport { parseArgs } from \"./args\";\nimport { run as runAffected } from \"./commands/affected\";\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 runCheckDocDrift } from \"./commands/check-doc-drift\";\nimport { runClearCache } from \"./commands/clear-cache\";\nimport { run as runDependencies } from \"./commands/dependencies\";\nimport { run as runDependents } from \"./commands/dependents\";\nimport { run as runDetectFeatures } from \"./commands/detect-features\";\nimport { run as runFeatureGraph } from \"./commands/feature-graph\";\nimport { run as runFindComplexFunctions } from \"./commands/find-complex-functions\";\nimport { run as runFindDuplicates } from \"./commands/find-duplicates\";\nimport { run as runFindSymbol } from \"./commands/find-symbol\";\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 { runInitConfig } from \"./commands/init-config\";\nimport { runInitSkill } from \"./commands/init-skill\";\nimport { run as runListTags } from \"./commands/list-tags\";\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 { CommandContext, CommandHandler } from \"./commands/types\";\nimport { runWorkspaceAffected } from \"./commands/workspace-affected\";\nimport { runWorkspacePackages } from \"./commands/workspace-packages\";\nimport { type ResolvedConfig, resolveConfig } from \"./config\";\nimport { buildGraph, loadGraphFromCache, saveGraphToCache } from \"./graph-loader\";\nimport { HELP_TEXT, QUERY_HELP_TEXT } from \"./help\";\nimport { watchAndRun } from \"./watch\";\n\n/** Commands that only read a snapshot and print — safe to re-run in a loop under `--watch`. */\nconst WATCHABLE_COMMANDS = new Set<CommandHandler>([\n runGraphOutput,\n runCallers,\n runDependencies,\n runDependents,\n runAffected,\n runFindUncovered,\n runFindComplexFunctions,\n runFindDuplicates,\n runCheckCycles,\n runCheckDocDrift,\n runListTags,\n]);\n\n/**\n * @description Handles the CLI's one-shot, no-graph-needed flags (help text, skill/config\n * scaffolding, cache clearing). Exits the process directly when one applies; does nothing\n * otherwise, letting `run()` continue to graph-building commands.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n */\nfunction handleEarlyExitFlags(parsed: ParsedArgs): void {\n if (parsed.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n }\n if (parsed.queryHelp) {\n console.log(QUERY_HELP_TEXT);\n process.exit(0);\n }\n if (parsed.initSkill) {\n runInitSkill(parsed.force);\n process.exit(0);\n }\n if (parsed.initConfig) {\n runInitConfig(parsed.force);\n process.exit(0);\n }\n if (parsed.clearCache) {\n runClearCache(parsed.cachePath);\n process.exit(0);\n }\n}\n\n/**\n * @description Handles `--workspace-packages`/`--workspace-affected`, which build a\n * `WorkspaceGraph` instead of the single-package `Graph` the rest of `run()` uses.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @param {ResolvedConfig} config - Resolved config, used for graph-build options.\n * @param {string} rootDir - Absolute project root.\n * @returns {Promise<boolean>} `true` if a workspace command was handled (caller should return immediately).\n */\nasync function runWorkspaceMode(\n parsed: ParsedArgs,\n config: ResolvedConfig,\n rootDir: string,\n): Promise<boolean> {\n if (!parsed.workspacePackages && !parsed.workspaceAffected) return false;\n\n if (parsed.watch) {\n console.error(\"Error: --watch is not supported with --workspace-packages/--workspace-affected\");\n process.exit(1);\n }\n\n const workspaceGraph = await createWorkspaceGraph(\n rootDir,\n configToGraphOptions(config.rawConfig),\n );\n\n if (parsed.workspacePackages) {\n runWorkspacePackages(workspaceGraph);\n } else {\n if (!parsed.file) {\n console.error(\"Error: --workspace-affected requires --file <path>\");\n process.exit(1);\n }\n runWorkspaceAffected(workspaceGraph, parsed.file);\n }\n return true;\n}\n\n/**\n * @description Whether any flag requires building/loading a graph without explicit entry\n * points (e.g. `--call-graph`, `--callers`), reusing whatever's cached from a prior run.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @returns {boolean} `true` if entry points may be omitted because a command flag implies scanning.\n */\nfunction computeAutoScan(parsed: ParsedArgs): boolean {\n return (\n parsed.proposeTags ||\n parsed.affectedTests ||\n parsed.applyTags ||\n parsed.callers ||\n parsed.dependencies ||\n parsed.dependents ||\n parsed.affected ||\n parsed.findUncovered ||\n parsed.listTags ||\n parsed.findComplexFunctions ||\n parsed.findDuplicates ||\n parsed.typeGraph ||\n parsed.moduleResponsibility ||\n parsed.callGraph ||\n parsed.findSymbol ||\n parsed.apiSurface\n );\n}\n\ninterface CommandEntry {\n name: string;\n flag: boolean;\n handler: CommandHandler;\n}\n\n/**\n * @description Resolves which command flag was passed to a handler. Errors out if more than\n * one command flag is set at once, rather than silently picking whichever comes first.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @returns {CommandHandler} The handler for the single active command flag, or the default\n * graph-output handler if none are set.\n */\nexport function resolveCommandHandler(parsed: ParsedArgs): CommandHandler {\n const commands: CommandEntry[] = [\n { name: \"--propose-tags\", flag: parsed.proposeTags, handler: runProposeTags },\n { name: \"--apply-tags\", flag: parsed.applyTags, handler: runApplyTags },\n { name: \"--affected-tests\", flag: parsed.affectedTests, handler: runAffectedTests },\n { name: \"--detect-features\", flag: parsed.detectFeatures, handler: runDetectFeatures },\n { name: \"--find-unused\", flag: parsed.findUnused, handler: runFindUnused },\n { name: \"--check-cycles\", flag: parsed.checkCycles, handler: runCheckCycles },\n { name: \"--check-doc-drift\", flag: parsed.checkDocDrift, handler: runCheckDocDrift },\n { name: \"--find-uncovered\", flag: parsed.findUncovered, handler: runFindUncovered },\n { name: \"--list-tags\", flag: parsed.listTags, handler: runListTags },\n { name: \"--callers\", flag: parsed.callers, handler: runCallers },\n { name: \"--dependencies\", flag: parsed.dependencies, handler: runDependencies },\n { name: \"--dependents\", flag: parsed.dependents, handler: runDependents },\n { name: \"--affected\", flag: parsed.affected, handler: runAffected },\n {\n name: \"--find-complex-functions\",\n flag: parsed.findComplexFunctions,\n handler: runFindComplexFunctions,\n },\n { name: \"--find-duplicates\", flag: parsed.findDuplicates, handler: runFindDuplicates },\n { name: \"--type-graph\", flag: parsed.typeGraph, handler: runTypeGraph },\n {\n name: \"--module-responsibility\",\n flag: parsed.moduleResponsibility,\n handler: runModuleResponsibility,\n },\n { name: \"--feature-graph\", flag: parsed.featureGraph, handler: runFeatureGraph },\n { name: \"--call-graph\", flag: parsed.callGraph, handler: runCallGraph },\n { name: \"--find-symbol\", flag: parsed.findSymbol, handler: runFindSymbol },\n { name: \"--api-surface\", flag: parsed.apiSurface, handler: runApiSurface },\n ];\n\n const active = commands.filter((command) => command.flag);\n if (active.length > 1) {\n console.error(\n `Error: conflicting flags: ${active.map((command) => command.name).join(\", \")} (pass only one command flag)`,\n );\n process.exit(1);\n }\n\n return active[0]?.handler ?? runGraphOutput;\n}\n\n/**\n * @description Runs `handler` once immediately, then re-runs it on every debounced file-change\n * batch, rebuilding and re-caching the graph each time.\n * @param {CommandHandler} handler - The resolved command handler to re-invoke.\n * @param {CommandContext} ctx - Base command context; only `graph` is replaced on each re-run.\n * @param {string} rootDir - Absolute project root, watched for changes.\n * @param {string[]} resolvedEntryPoints - Entry points to rebuild the graph from.\n * @param {string} resolvedCachePath - Where to persist the rebuilt graph.\n * @param {boolean} silent - Suppresses build progress output when true.\n * @param {MokoshConfig} rawConfig - Config used to derive graph-build options on rebuild.\n */\nasync function runWatchLoop(\n handler: CommandHandler,\n ctx: CommandContext,\n rootDir: string,\n resolvedEntryPoints: string[],\n resolvedCachePath: string,\n silent: boolean,\n rawConfig: MokoshConfig,\n): Promise<void> {\n if (!WATCHABLE_COMMANDS.has(handler)) {\n console.error(\n \"Error: --watch is only supported with the default output, --query, --callers, --dependencies, --dependents, --affected, --find-uncovered, --find-complex-functions, --find-duplicates, --check-cycles, --check-doc-drift, and --list-tags\",\n );\n process.exit(1);\n }\n watchAndRun(rootDir, 300, async () => {\n const freshGraph = await buildGraph(rootDir, resolvedEntryPoints, ctx.graph, {\n silent,\n ...configToGraphOptions(rawConfig),\n });\n saveGraphToCache(freshGraph, resolvedCachePath);\n console.log(`--- rebuilt at ${new Date().toISOString()} ---`);\n await handler({ ...ctx, graph: freshGraph });\n });\n}\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 handleEarlyExitFlags(parsed);\n\n const config = resolveConfig(parsed);\n applyConfig(config.rawConfig);\n const { rootDir, resolvedEntryPoints, resolvedCachePath, scanOptions } = config;\n\n if (await runWorkspaceMode(parsed, config, rootDir)) return;\n\n const autoScan = computeAutoScan(parsed);\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 !parsed.findUnused &&\n !parsed.detectFeatures &&\n !parsed.checkCycles &&\n !parsed.checkDocDrift\n ) {\n console.error(\"Error: No entry points provided\");\n process.exit(1);\n }\n graph = await buildGraph(rootDir, resolvedEntryPoints, graph, {\n silent: parsed.silent,\n ...configToGraphOptions(config.rawConfig),\n });\n\n saveGraphToCache(graph, resolvedCachePath);\n }\n\n const ctx: CommandContext = {\n graph,\n rootDir,\n entryPoints: resolvedEntryPoints.map((entryPath) => entryPath.replace(`${rootDir}/`, \"\")),\n scanOptions,\n rawConfig: config.rawConfig,\n featureThreshold: parsed.featureThreshold,\n queryStr: parsed.query,\n mermaidOutput: parsed.mermaid,\n plain: parsed.plain,\n excludeTests: parsed.excludeTests,\n file: parsed.file,\n typeFilter: parsed.typeFilter,\n filterPaths: parsed.filterPaths,\n minOutDegree: parsed.minOutDegree,\n functionName: parsed.functionName,\n dryRun: parsed.dryRun,\n depth: parsed.depth,\n cached: parsed.cached,\n changedSymbols: parsed.changedSymbols,\n withMeta: parsed.withMeta,\n withEdgeDetail: parsed.withEdgeDetail,\n metric: parsed.metric,\n complexityThreshold: parsed.complexityThreshold,\n limit: parsed.limit,\n slim: parsed.slim,\n testsOnly: parsed.testsOnly,\n minDuplicateLines: parsed.minDuplicateLines,\n };\n\n const handler = resolveCommandHandler(parsed);\n\n if (parsed.watch) {\n await runWatchLoop(\n handler,\n ctx,\n rootDir,\n resolvedEntryPoints,\n resolvedCachePath,\n parsed.silent,\n config.rawConfig,\n );\n return;\n }\n\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","configToGraphOptions","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","computeGraphHash","graph","entries","pathA","pathB","filePath","node","hash","i","buildChangeImpactCache","impact","affected","queryChangeImpact","cache","import_promises","import_node_path","import_node_fs","import_node_path","import_js_yaml","LOCK_FILE_NAMES","stripVersionSuffix","descriptor","lastAt","parseYarnDescriptors","line","part","trimmed","parsePnpmId","id","pkgVersion","raw","tryParseYarnBerry","content","lock","loadYaml","result","key","value","name","parseYarnClassic","block","lines","header","names","version","parsePackageLock","filePath","fs","pkgPath","pkgData","parseYarnLock","berryResult","parsePnpmLock","versionData","loadLockFile","rootDir","parsers","candidates","filename","parser","path","STYLE_FILE_TYPES","getDuplicateFamily","fileType","hashWindow","text","hash","i","hashWindows","tokens","windowSize","hashes","t","extendChain","hashesByFile","a","b","hashesA","hashesB","extra","isChainStart","ExactOccurrenceUnionFind","key","root","cur","next","keyA","keyB","rootA","rootB","occurrenceKey","occ","STRUCTURAL_PUNCTUATION","structuralPunctuationRatio","start","length","count","findDuplicateGroups","files","minLines","maxPunctuationRatio","tokensByFile","buckets","file","loc","bucket","pairMatches","locations","j","tokensA","lo","hi","tokensB","occA","occB","lines","clusters","occurrenceByKey","keysByRoot","keys","groups","occurrences","pm","import_postcss","less","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","extractLessVariableExports","exports","tags","name","parseLessContent","import_postcss_scss","isScssPrivateName","name","isScssExternal","specifier","parseScssParams","params","specMatch","alias","extractScssExports","root","exports","tags","node","signature","parseScssContent","content","filePath","scssParse","imports","edge","normalizeValue","value","rootFor","file","parseScssContent","parseLessContent","parseCssContent","collectRuleBlocks","root","blocks","rule","decls","node","firstDecl","lastDecl","startLine","endLine","signature","decl","findStyleBlockDuplicates","files","minLines","minDeclarations","buckets","block","bucket","groups","j","a","b","occA","occB","COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","fileType","ignoreLiterals","stripped","tokens","line","lastIndex","match","i","raw","text","STRUCTURAL_STYLE_TYPES","isLockFile","relPath","LOCK_FILE_NAMES","path","isUnderIgnoredDir","ignoreDirs","ignored","segment","findDuplicates","graph","rootDir","options","minLines","windowSize","ignoreLiterals","maxPunctuationRatio","limit","DEFAULT_IGNORE_DIRS","nodes","node","structuralStyleFiles","filesByFamily","source","family","getDuplicateFamily","fileTokens","tokenize","bucket","groups","findStyleBlockDuplicates","group","findDuplicateGroups","a","b","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","EXPORT_TRACKING_TYPES","IMPORT_SYMBOL_TYPES","CALL_EDGE_TYPES","getLanguageCoverage","graph","counts","node","type","fileCount","a","b","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","SymbolTraversalContext","startPath","affectedSymbols","visitedNode","childPath","currentSymbols","importEdge","imp","importedSymbols","relevantSymbols","sym","existing","symbol","getNodeMeta","graph","path","node","exportedSym","getDependencies","file","depth","deps","_depth","parentPath","edge","importEdge","getDependents","dependents","getAffected","options","testsOnly","changedSymbols","ctx","SymbolTraversalContext","affected","isTest","tag","getCallers","withEdgeDetail","callers","entry","callEdge","findComplexFunctions","metric","threshold","limit","fn","a","b","slimSerialize","filtered","imp","summarizeWorkspacePackages","wg","pkgDeps","packages","pkg","hasCoverageData","inferRole","node","filePath","seg","fileBasename","segment","name","buildResponsibilityGraph","graph","featureOptions","featureGraph","buildFeatureGraph","fileToHub","featureName","domain","filePath","result","node","hub","inferRole","exportedSym","findSymbol","graph","name","matches","node","exportedSym","CALL_EDGE_TYPES","callers","other","edge","importers","getDependents","dependent","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","loadYaml","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","getMonorepoDetectors","registerMonorepoDetector","turborepoDetector","nxDetector","pnpmDetector","yarnDetector","npmDetector","detectMonorepo","rootDir","detectors","getMonorepoDetectors","abs","path","allPackages","detectedTypes","detector","pkgs","pkg","packages","WorkspaceGraph","_WorkspaceGraph","monorepoRoot","type","pkg","graph","relPath","deps","pkgDeps","node","imp","ownerPkg","ownerEntry","result","data","wg","nodes","nodeMap","Graph","parserRegistry","registerParser","type","parser","getParserForType","matchesStr","nodeValue","queryValue","matchesPath","nodePath","queryPath","matchCategory","node","query","matchType","matchLastAuthor","matchPath","matchIsExternal","importEdge","matchTags","positiveTags","tag","negativeTags","structuredTag","matchAllTags","matchImportsFile","matchImportedBy","reverseIndex","importerPath","matchMinImports","matchMaxImports","matchMinSize","matchMaxSize","matchHasDocstring","matchIsDocumented","matchIsStale","matchMinCoverage","matchMaxCoverage","matchMinExportUsage","matchMaxExportUsage","matchMinComplexity","matchMaxComplexity","matchMinCognitiveComplexity","matchMaxCognitiveComplexity","matchMinCommits","matchMaxCommits","matchAny","subQuery","matchNode","NODE_MATCHERS","matcher","filterGraph","graph","query","reverseIndex","node","imp","arr","filteredNodes","matchNode","nodePaths","resultNodes","direction","nodeA","nodeB","diff","cycle","path","applyClause","query","part","colonIdx","key","value","parseAnyGroup","inner","subQueries","clause","trimmedClause","subQuery","parseQuery","queryString","parts","trimmedPart","import_promises","import_node_path","import_node_path","import_typescript","import_node_path","import_node_path","import_typescript","ANNOTATABLE_NAMES","at","arr","index","value","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","stripAtPrefix","raw","applyAtPrefixedTagProp","absPath","path","firstCall","rawExisting","sortedTags","literal","CypressStrategy","absPath","TS_EXTENSIONS","path","source","tags","applyAtPrefixedTagProp","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","PlaywrightStrategy","absPath","TS_EXTENSIONS","path","source","tags","applyAtPrefixedTagProp","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","sum","import_node_path","import_typescript","LEGACY_BLOCK_REGEX","VitestStrategy","absPath","TS_EXTENSIONS","path","source","tags","stripped","sf","ts","calls","findTopLevelCalls","firstCall","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_piscina","import_node_child_process","DefaultGitProvider","allFiles","args","filePath","error","COMMIT_HEADER_FORMAT","MAX_LOG_BUFFER_BYTES","walkNameStatusLog","output","onFile","authorEmail","timestampMs","line","email","at","parts","status","getRepoGitStats","rootDir","stats","recentOutput","existing","fullOutput","_authorEmail","import_node_path","getFileType","filePath","path","isStyleFile","specifier","ext","import_coffeescript","stripQuotes","value","extractTagAnnotations","content","tags","tagRegex","match","classifyTestOrLogic","filePath","lower","stringLiteralValue","node","stripQuotes","identifierName","edgeFromImportDeclaration","filePath","specifier","isStyleFile","edgeFromRequireCall","isRequire","exportsFromAssignment","assignNode","propertyName","rhs","className","property","name","bareName","exportsFromNamedClause","clause","visitNode","imports","exports","edge","base","traverse","key","child","c","parseCoffeeScript","content","tags","extractTagAnnotations","category","classifyTestOrLogic","coffee","seenNames","dedupedExports","symbol","import_gherkin","import_messages","uuidFn","parseGherkin","_filePath","content","rawTags","builder","matcher","gherkinDocument","tag","child","example","ruleChild","error","name","registerParser","import_node_path","import_go","childrenOf","node","result","child","lineAt","content","pos","line","i","computeCyclomaticComplexity","rootNode","content","complexity","walk","node","text","child","computeCognitiveComplexity","cognitive","depth","isElseIf","name","kids","childrenOf","bodyDepth","cond","thenBlock","k","elseIndex","after","computeComplexity","receiverTypeName","methodDecl","receiverParam","typeNode","collectFunctionComplexity","tree","results","cursor","nameNode","cognitiveComplexity","lineAt","methodName","receiver","TAG_RE","BUILD_NEW_RE","BUILD_OLD_RE","parseGo","filePath","content","imports","exportMap","tags","buildTags","packageIdents","tree","cursor","text","tagM","newBuild","extractBuildTokens","oldBuild","stringNode","specifier","aliasNode","ident","nameNode","name","importsTestingPkg","importEdge","category","path","allTagNames","complexity","cognitiveComplexity","computeComplexity","functions","collectFunctionComplexity","rawCallEdges","collectRawCallEdges","edges","walkBody","node","callerName","callee","pkgIdentNode","fieldNode","pkgIdent","toSpecifier","to","child","body","methodName","receiver","receiverTypeName","expr","out","tok","import_livescript","identifierName","node","exportsFromValue","rhs","className","prop","name","bareName","extractExports","type","chain","base","propertyName","POSITIONAL_KEYS","extractEdge","filePath","raw","specifier","stripQuotes","isStyleFile","call","collectEdges","exports","edges","edge","key","child","c","parseLiveScript","content","tags","extractTagAnnotations","category","classifyTestOrLogic","imports","ls","seenNames","dedupedExports","symbol","import_luaparse","collectRequireEdges","ast","filePath","importEdges","visitNode","node","specifier","requireArgument","stripQuotes","isStyleFile","key","childValue","childNode","collectModuleTableNames","topLevelStatements","moduleTableNames","statement","variable","index","collectReturnTableExports","lastStatement","returnedTable","exportedSymbols","field","collectExports","seenNames","symbol","parseLua","content","tagNames","extractTagAnnotations","category","classifyTestOrLogic","imports","exports","luaparse","name","EXTERNAL_LINK_PREFIXES","CODE_EXTENSIONS","PATH_TOKEN_PATTERN","processorPromise","getProcessor","unified","remarkParse","isExternalLink","url","trimmed","p","edgesFromCodeText","text","filePath","matches","specifier","walk","node","edges","child","parseMarkdown","content","tree","seen","edge","import_node_path","import_python","computeCyclomaticComplexity","rootNode","complexity","walk","node","childrenOf","k","child","computeCognitiveComplexity","cognitive","depth","name","kids","branchIndex","i","kw","isElseIf","bodyDepth","cond","body","computeComplexity","collectFunctionComplexity","tree","content","results","recordFunction","cognitiveComplexity","lineAt","walkChildren","classNameNode","className","fnNameNode","fnName","nameNode","TEST_LIBS","parsePython","filePath","content","imports","exports","tags","baseName","path","tree","cursor","tagMatch","edge","extractImportEdges","parentNode","nameNode","target","category","resolveCategory","complexity","cognitiveComplexity","computeComplexity","functions","collectFunctionComplexity","rawCallEdges","collectRawCallEdges","name","node","src","first","extractFromImport","extractBareImport","fromKw","importKw","rawModule","importedNames","collectImportedNames","dotCount","modulePart","makeEdge","prefix","edges","childNode","modName","start","names","buildImportSymbolMap","symbolMap","child","importedName","localName","aliasNode","specifier","importSymbols","walkBody","callerName","calleeNode","calleeName","toSpecifier","walkChildren","walk","classNameNode","className","body","fnNameNode","fnName","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","parseStylusImports","content","filePath","imports","atRequirePattern","match","specifier","bareImportPattern","detectStylusCategory","stylusLib","astNode","parseStyleFile","filePath","content","fileType","getFileType","imports","parseStylusImports","detectStylusCategory","root","exports","tags","parseScssContent","detectCssBarrel","parseLessContent","parseCssContent","type","parser","path","content","parseCodeFile","parseStyleFile","parseCoffeeScript","parseLiveScript","parseLua","parsePython","parseGo","parseGherkin","parseMarkdown","registerParser","parseFile","filePath","fileType","getFileType","getParserForType","import_node_path","enrichCoverage","nodes","coverageMap","node","pct","enrichLibraryTags","imports","tags","imp","libName","existingTag","enrichTestedBy","target","enrichDocDrift","round4","value","enrichExportUsage","ratios","ratio","sum","addUniqueTag","name","kind","addFilenameTag","testNode","importEdge","toPath","filenameTag","path","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_path","MarkdownLangResolver","_currentFile","specifier","rootDir","resolveLocal","resolved","path","import_node_fs","import_node_path","PythonLangResolver","_currentFile","specifier","rootDir","_resolveLocal","pyPath","path","pyFile","isFile","initFile","filePath","fs","import_node_path","EXTERNAL_PREFIXES","StyleLangResolver","currentFile","specifier","_rootDir","resolveLocal","prefix","resolved","dir","path","base","partialSpecifier","partialResolved","DefaultResolver","rootDir","options","PythonLangResolver","LuaLangResolver","GoLangResolver","StyleLangResolver","MarkdownLangResolver","currentFile","specifier","aliased","resolved","resolveLocal","cf","spec","lr","ext","locals","workspace","dir","path","fullPath","isExternal","extensions","esmMatch","strippedPath","candidatePath","indexP","initP","filePath","fs","alias","match","substitutions","searchDir","tsconfigPath","paths","regex","pattern","wildcardMatch","baseDir","sub","resolvedSub","pkgName","pkgRoot","subPath","base","candidate","abs","deepResolved","CONVENTIONAL_TEST_DIR_NAMES","WALK_IGNORE_DIRS","DEFAULT_MIN_FILES_FOR_POOL","commonAncestorDir","absPaths","rootDir","segmentLists","p","path","common","segments","i","candidate","rel","GraphBuilder","previousGraph","resolver","progressCallback","enableGitStats","coverageMap","parallelParsing","DefaultResolver","loadLockFile","entryPoints","entryPaths","entry","getRepoGitStats","entryPath","enrichTestNodeTags","enrichTestedBy","enrichExportUsage","enrichDocDrift","enrichCoverage","Graph","opts","minFiles","workerFilename","Piscina","err","dir","threshold","count","stack","current","entries","fs","scanRoot","patterns","getTestPatterns","matchesTest","pattern","parent","name","matches","fullPath","filePath","batch","stats","relativePath","node","enrichLibraryTags","cachedNode","parsed","callEdges","content","parseFile","getFileType","rawCallEdges","rce","resolved","imports","exports","tags","category","description","complexity","cognitiveComplexity","functions","git","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","abs","path","GraphBuilder","DefaultResolver","createWorkspaceGraph","layout","detectMonorepo","pkgs","pkg","workspaceMap","wg","WorkspaceGraph","graph","getAllProjectFiles","files","ignoreDirs","DEFAULT_IGNORE_DIRS","extensions","DEFAULT_EXTENSIONS","walk","dir","entries","fs","entry","fullPath","import_node_path","import_node_util","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","resolveRootDir","cliTokens","i","path","parseOptionalInt","raw","parseCsv","part","OPTIONS","STRING_FLAGS","v","k","sanitizeTokens","result","token","valueToken","parseArgs","rootDir","defaultCachePath","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","values","positionals","nodeParseArgs","cacheValue","configValue","metricRaw","run","ctx","graph","file","testsOnly","changedSymbols","cached","withMeta","annotate","paths","p","getNodeMeta","impactCache","buildChangeImpactCache","allAffected","queryChangeImpact","affected","filePath","result","getAffected","import_node_path","TEST_PATTERNS","getTestFiles","allFiles","filePath","base","path","pattern","resolveChangedFiles","rootDir","DefaultGitProvider","run","ctx","graph","rootDir","scanOptions","featureThreshold","rawConfig","changedFiles","resolveChangedFiles","node","getTestFiles","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","affectedTests","proposeAffectedTests","run","ctx","graph","rootDir","entryPoints","eps","detectAllEntryPoints","surface","buildApiSurface","run","ctx","graph","rootDir","scanOptions","dryRun","plain","rawConfig","allFiles","getAllProjectFiles","createImportMap","getTestFiles","configToGraphOptions","result","applyTags","run","ctx","graph","functionName","result","queryCallGraph","run","ctx","graph","file","plain","depth","withEdgeDetail","callers","getCallers","caller","run","ctx","graph","cycles","cycle","run","ctx","graph","staleDocs","node","doc","staleFor","import_node_fs","runClearCache","cachePath","fs","run","ctx","graph","file","depth","withMeta","deps","getDependencies","dependencies","dep","getNodeMeta","run","ctx","graph","file","withMeta","deps","getDependents","dependents","dep","getNodeMeta","run","ctx","graph","rootDir","scanOptions","featureThreshold","rawConfig","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","featureMap","detectFeatures","features","featureA","featureB","run","ctx","graph","rootDir","scanOptions","minOutDegree","rawConfig","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","featureGraph","buildFeatureGraph","features","run","ctx","graph","metric","complexityThreshold","limit","threshold","functions","findComplexFunctions","run","ctx","graph","rootDir","scanOptions","minDuplicateLines","limit","minLines","ignoreDirs","DEFAULT_IGNORE_DIRS","groups","findDuplicates","run","ctx","graph","functionName","plain","matches","findSymbol","match","run","ctx","graph","featureThreshold","rawConfig","plain","threshold","hasCoverageData","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","slim","serialized","query","parseQuery","filterGraph","filteredGraph","Graph","MermaidExporter","cycles","languageCoverage","getLanguageCoverage","slimSerialize","import_node_fs","import_node_path","import_node_fs","import_node_path","findTemplateDir","startDir","subpathSegments","markerFile","dir","i","candidate","path","fs","parent","runInitConfig","force","cwd","templateDir","findTemplateDir","src","path","dest","fs","import_node_fs","import_node_path","TARGETS","runInitSkill","force","cwd","templateDir","findTemplateDir","target","src","path","dest","relDest","fs","run","ctx","graph","plain","counts","node","tag","tags","name","count","a","b","run","ctx","graph","filterPaths","minOutDegree","respGraph","buildResponsibilityGraph","modules","modulePath","run","ctx","graph","rootDir","scanOptions","featureThreshold","plain","rawConfig","changedFiles","resolveChangedFiles","allFiles","getAllProjectFiles","createImportMap","getTestFiles","configToGraphOptions","tags","proposeTags","run","ctx","graph","typeFilter","typeGraph","buildTypeGraph","result","queryTypeGraph","types","runWorkspaceAffected","wg","file","affected","runWorkspacePackages","wg","summarizeWorkspacePackages","import_node_path","resolveCachePath","cachePath","defaultCachePath","configCachePath","rootDir","path","resolveConfig","parsed","entryPoints","configPath","config","loadMokoshConfig","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","resolvedEntryPoints","resolvedCachePath","scanOptions","import_node_fs","import_node_path","loadGraphFromCache","cachePath","fs","raw","Graph","saveGraphToCache","graph","cacheDir","path","buildGraph","rootDir","entryPoints","cachedGraph","options","configToGraphOptions","createImportMap","HELP_TEXT","QUERY_HELP_TEXT","import_node_fs","IGNORE_WATCH","watchAndRun","rootDir","debounceMs","rerun","timer","running","pending","trigger","watcher","fs","_event","filename","IGNORE_WATCH","err","WATCHABLE_COMMANDS","run","handleEarlyExitFlags","parsed","HELP_TEXT","QUERY_HELP_TEXT","runInitSkill","runInitConfig","runClearCache","runWorkspaceMode","config","rootDir","workspaceGraph","createWorkspaceGraph","configToGraphOptions","runWorkspacePackages","runWorkspaceAffected","computeAutoScan","resolveCommandHandler","active","command","runWatchLoop","handler","ctx","resolvedEntryPoints","resolvedCachePath","silent","rawConfig","watchAndRun","freshGraph","buildGraph","saveGraphToCache","argv","parseArgs","resolveConfig","applyConfig","scanOptions","autoScan","graph","loadGraphFromCache","Graph","entryPath","run","err"]}
|
|
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/duplication/index.ts","../src/parser/lockfile.ts","../src/graph/duplication/families.ts","../src/graph/duplication/shingle.ts","../src/parser/style/css.ts","../src/parser/style/scss.ts","../src/graph/duplication/style-blocks.ts","../src/graph/duplication/tokenizer.ts","../src/graph/features/index.ts","../src/graph/features/feature-graph.ts","../src/graph/language-support.ts","../src/graph/analyzer.ts","../src/graph/model.ts","../src/graph/symbol-traversal.ts","../src/graph/queries.ts","../src/graph/responsibility/infer-role.ts","../src/graph/responsibility/index.ts","../src/graph/symbol.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/graph/workspace-model.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/file-type.ts","../src/parser/lang/coffee.ts","../src/parser/utils.ts","../src/parser/lang/gherkin.ts","../src/parser/lang/go.ts","../src/parser/complexity/lezer-utils.ts","../src/parser/complexity/go.ts","../src/parser/lang/ls.ts","../src/parser/lang/lua.ts","../src/parser/lang/markdown.ts","../src/parser/lang/python.ts","../src/parser/complexity/python.ts","../src/parser/lang/typescript.ts","../src/parser/complexity.ts","../src/parser/tagging/index.ts","../src/parser/style/barrel.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/markdown.ts","../src/graph/lang-resolvers/python.ts","../src/graph/lang-resolvers/style.ts","../src/tags/proposer.ts","../src/index.ts","../src/cli/args.ts","../src/cli/const.ts","../src/cli/commands/affected.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/check-doc-drift.ts","../src/cli/commands/clear-cache.ts","../src/cli/commands/dependencies.ts","../src/cli/commands/dependents.ts","../src/cli/commands/detect-features.ts","../src/cli/commands/feature-graph.ts","../src/cli/commands/find-complex-functions.ts","../src/cli/commands/find-duplicates.ts","../src/cli/commands/find-symbol.ts","../src/cli/commands/find-uncovered.ts","../src/cli/commands/find-unused.ts","../src/cli/commands/graph-output.ts","../src/cli/commands/init-config.ts","../src/cli/commands/template-dir.ts","../src/cli/commands/init-skill.ts","../src/cli/commands/list-tags.ts","../src/cli/commands/module-responsibility.ts","../src/cli/commands/propose-tags.ts","../src/cli/commands/type-graph.ts","../src/cli/commands/workspace-affected.ts","../src/cli/commands/workspace-packages.ts","../src/cli/config.ts","../src/cli/graph-loader.ts","../src/cli/help.ts","../src/cli/watch.ts","../src/watch-ignore.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 type { ParallelParsingOption } from \"./graph/builder\";\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 /**\n * Explicit path-alias map for import resolution, same shape as tsconfig's\n * `compilerOptions.paths` (e.g. `{ \"@app/*\": [\"src/app/*\"] }`). Takes precedence over\n * any aliases declared in `tsconfig.json`. Useful for JS-only projects without a\n * tsconfig, or to mirror Vite/webpack alias configs. Substitution paths are resolved\n * relative to the project root.\n */\n pathAliases?: Record<string, 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 * Controls worker-pool offloading of file parsing (see docs/adr-010-parallel-parsing.md).\n * `true`/unset (default) enables it once a cheap pre-scan finds at least `minFiles`\n * (default 20) files; parsing a file is fast enough in most repos that the pool's\n * per-thread startup cost only pays off past roughly 600-700 files, so small/typical\n * repos may see slightly slower builds under the default — set `false` to always parse\n * in-process, or pass `{ minFiles, maxThreads }` to raise the threshold instead.\n */\n parallelParsing?: ParallelParsingOption;\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\n/**\n * @description Extracts the subset of `MokoshConfig` fields that affect graph\n * construction (`gitStats`, `parallelParsing`, `pathAliases`) into a plain options\n * object, ready to spread into `createImportMap`/`createWorkspaceGraph` calls. Single\n * source of truth for this mapping so every graph-building call site — CLI, MCP,\n * and secondary command-level rebuilds — stays in sync as new config fields are added.\n * @param config - The loaded config, or `undefined` when none has been loaded yet.\n * @returns Graph-build options with defaults applied (`gitStats` defaults to `false`).\n */\nexport function configToGraphOptions(config: MokoshConfig | undefined): {\n gitStats: boolean;\n parallelParsing: ParallelParsingOption | undefined;\n pathAliases: Record<string, string[]> | undefined;\n} {\n return {\n gitStats: config?.gitStats ?? false,\n parallelParsing: config?.parallelParsing,\n pathAliases: config?.pathAliases,\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 \".md\",\n \".mdx\",\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}\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, Go, and Python files\n * (see `CALL_EDGE_TYPES` in `../language-support`). Functions in other language\n * 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","/**\n * Cross-file duplicate-code detection. Two matching strategies, picked per file by `FileType`:\n * CSS/Less/SCSS route through `findStyleBlockDuplicates` (`style-blocks.ts`), a structural\n * comparator over PostCSS rule bodies; everything else (including Stylus, which has no shared\n * PostCSS AST here) runs the generic tokenize → shingle → chain-merge pipeline. See\n * docs/adr-012-duplicate-detection.md for why the shingle pipeline is built in-house instead of\n * adopting jscpd, and docs/adr-013-duplicate-detection-noise-reduction.md for why CSS-family\n * matching moved off it.\n *\n * `graph.nodes` is not a reliable ignore-rule-filtered file list: `DEFAULT_IGNORE_DIRS` and\n * extension filtering only gate `GraphBuilder`'s own FS-walk discovery passes, not files that\n * become reachable via a resolved reference (an import, or — for Markdown — a code-span file\n * reference per ADR-009). A doc that merely *mentions* `dist/parse-worker.js` in backticks can\n * pull that build artifact into the graph as a real node, ignore-dir or extension notwithstanding.\n * So this module applies its own filtering rather than trusting the graph's membership.\n *\n * On large repos, tokenizing thousands of files in-process can itself take long enough to blow\n * past an MCP client's response timeout, so tokenizing is optionally offloaded to a `piscina`\n * worker pool — same pattern and same threshold rationale as `GraphBuilder`'s `parseFile` pool,\n * see docs/adr-010-parallel-parsing.md and docs/adr-014-duplicate-detection-scale.md.\n */\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport Piscina from \"piscina\";\nimport { DEFAULT_IGNORE_DIRS } from \"../../const\";\nimport { LOCK_FILE_NAMES } from \"../../parser/lockfile\";\nimport type { FileType } from \"../../types/parse\";\nimport type { Graph } from \"../model\";\nimport { type DuplicateFamily, getDuplicateFamily } from \"./families\";\nimport { type DuplicateGroup, type FileTokens, findDuplicateGroups } from \"./shingle\";\nimport { findStyleBlockDuplicates, type StyleSourceFile } from \"./style-blocks\";\nimport { tokenize } from \"./tokenizer\";\n\n/** Below this many candidate files, worker-pool spin-up cost outweighs the parallelism benefit —\n * tokenize in-process instead. Mirrors `GraphBuilder`'s `DEFAULT_MIN_FILES_FOR_POOL`. */\nconst DEFAULT_MIN_FILES_FOR_POOL = 20;\n\n/** Configures whether/how tokenizing is offloaded to a `piscina` worker pool. `false` always\n * tokenizes in-process. */\nexport type ParallelTokenizingOption = boolean | { minFiles?: number; maxThreads?: number };\n\nexport type { DuplicateFamily } from \"./families\";\nexport type { DuplicateGroup, DuplicateOccurrence } from \"./shingle\";\nexport type { StyleSourceFile } from \"./style-blocks\";\n\n/** CSS-family types with a shared PostCSS AST available — routed through the structural rule-body\n * comparator instead of the generic token-shingle pipeline. Stylus has no such AST here, so it\n * stays on the token-shingle path (still isolated to the `\"style\"` family, see `families.ts`). */\nconst STRUCTURAL_STYLE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\"css\", \"scss\", \"less\"]);\n\nexport interface FindDuplicatesOptions {\n /** Minimum duplicated block size, in source lines, to report (default 6). */\n minLines?: number | undefined;\n /** Shingle window size, in tokens — the smallest duplicate the scan can detect (default 15). */\n windowSize?: number | undefined;\n /** When true (default), string/number literals are normalized too, so only structural shape\n * — not the specific values used — drives a match. Set false for stricter, Type-1-only matching. */\n ignoreLiterals?: boolean | undefined;\n /** Maximum fraction of a token-shingled block's window that may be object/array-literal\n * structural punctuation (`{ } : , [ ]`) (default 0.5) — gates out blocks that are mostly\n * schema/object-literal shape (e.g. MCP tool `inputSchema` boilerplate repeated across\n * unrelated tool definitions) rather than substantive shared logic. Does not apply to the\n * CSS/Less/SCSS structural comparator, which already matches on literal declaration content.\n * Set to 1 to disable. See docs/adr-013-duplicate-detection-noise-reduction.md. */\n maxPunctuationRatio?: number | undefined;\n /** Skip a hash bucket's O(k²) pairwise comparison once it holds more than this many locations\n * (default 400) — bounds worst-case scan time on large repos where a single ubiquitous token\n * window (a common import line, a boilerplate header) would otherwise blow past what a single\n * scan can finish in. Set `Infinity` to disable. See docs/adr-014-duplicate-detection-scale.md. */\n maxBucketSize?: number | undefined;\n /** Caps the number of duplicate blocks returned, largest-first (default 50). */\n limit?: number | undefined;\n /** Directory names to exclude, matched against any path segment (default `DEFAULT_IGNORE_DIRS`\n * — `node_modules`, `dist`, `.git`, `mokosh-cache`, `coverage`, etc.). Pass `[]` to disable. */\n ignoreDirs?: readonly string[] | undefined;\n /** Controls worker-pool offloading of per-file tokenizing (default `true`): offloads once the\n * candidate file count reaches `minFiles` (default 20, matching `GraphBuilder`'s parse pool);\n * `false` always tokenizes in-process; an object overrides `minFiles`/`maxThreads`. See\n * docs/adr-014-duplicate-detection-scale.md. */\n parallelTokenizing?: ParallelTokenizingOption | undefined;\n}\n\nexport interface FindDuplicatesResult {\n /** Duplicate blocks, largest-first, capped at `limit`. */\n groups: DuplicateGroup[];\n /** How many hash buckets were skipped for exceeding `maxBucketSize` — a non-zero count means\n * results may under-report duplication that's unusually widespread (see `maxBucketSize`). */\n skippedBuckets: number;\n}\n\n/**\n * @description Lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) contain large\n * blocks of genuinely repeated JSON/YAML structure — every dependency entry looks like every\n * other one — but that's not code duplication, and it swamps real findings by sheer line\n * count. Excluded regardless of `ignoreDirs`, since lock files sit at the project root, not in\n * an ignorable directory.\n * @param relPath - Project-relative file path.\n * @returns Whether `relPath`'s basename is a known lock file.\n */\nfunction isLockFile(relPath: string): boolean {\n return LOCK_FILE_NAMES.includes(path.basename(relPath));\n}\n\n/**\n * @description Whether any path segment of `relPath` matches an ignored directory name.\n * @param relPath - Project-relative file path.\n * @param ignoreDirs - Directory names to treat as excluded.\n * @returns `true` if `relPath` lives under one of `ignoreDirs`.\n */\nfunction isUnderIgnoredDir(relPath: string, ignoreDirs: readonly string[]): boolean {\n if (ignoreDirs.length === 0) return false;\n const ignored = new Set(ignoreDirs);\n return relPath.split(path.sep).some((segment) => ignored.has(segment));\n}\n\n/**\n * @description Scans every file already present in `graph` for cross-file (and within-file)\n * duplicated code. CSS/Less/SCSS files are compared structurally — by their rule bodies'\n * literal, ordered `property: value` declarations, independent of selector name — via\n * {@link findStyleBlockDuplicates}. Every other language (TS/JS, Python, Go, CoffeeScript,\n * LiveScript, Lua, Gherkin, Markdown, and Stylus, which has no shared PostCSS AST here) runs\n * the generic token-shingling pipeline instead: comments are stripped per `FileType`,\n * identifiers (and, by default, literals) are normalized to placeholders so renamed-variable\n * copies still match, then a sliding token window is hashed and chain-merged into contiguous\n * blocks. Token-shingled files are additionally partitioned into language families\n * ({@link getDuplicateFamily} — `\"style\"` for Stylus, `\"code\"` for everything else) so\n * matching never crosses that boundary — see docs/adr-013-duplicate-detection-noise-reduction.md.\n * @param graph - The graph to scan; its node paths (already ignore-rule-filtered) are the file\n * list, re-read from disk since duplication data isn't cached on `FileNode`.\n * @param rootDir - Absolute project root that graph paths are relative to.\n * @param options - `minLines`/`windowSize` tune token-shingle sensitivity (`minLines` also caps\n * CSS/Less/SCSS block size); `ignoreLiterals` toggles Type-2 vs Type-1 matching for the\n * token-shingle path only (CSS/Less/SCSS always match on literal declaration content);\n * `maxPunctuationRatio` gates out token-shingle blocks that are mostly object/array-literal\n * structural punctuation (e.g. schema/object-literal boilerplate) rather than substantive\n * shared logic; `maxBucketSize` bounds worst-case scan time on large repos by skipping\n * pathologically common hash buckets; `ignoreDirs` excludes files under matching directory\n * names; `limit` caps results; `parallelTokenizing` offloads per-file tokenizing to a worker\n * pool once the candidate file count is large enough to be worth it. Lock files are always\n * excluded, independent of `ignoreDirs`.\n * @returns `groups` — duplicate blocks (each tagged with its `family`), two or more occurrences\n * per block, every block that pairwise chain-matches another clustered into one group instead\n * of one per pair, sorted largest-first across all families — plus `skippedBuckets` (see\n * {@link FindDuplicatesResult}).\n */\nexport async function findDuplicates(\n graph: Graph,\n rootDir: string,\n options: FindDuplicatesOptions = {},\n): Promise<FindDuplicatesResult> {\n const {\n minLines = 6,\n windowSize = 15,\n ignoreLiterals = true,\n maxPunctuationRatio = 0.5,\n maxBucketSize,\n limit = 50,\n ignoreDirs = DEFAULT_IGNORE_DIRS,\n parallelTokenizing = true,\n } = options;\n\n const nodes = [...graph.nodes.values()].filter(\n (node) => !isLockFile(node.path) && !isUnderIgnoredDir(node.path, ignoreDirs),\n );\n\n const pool = createTokenizingPool(parallelTokenizing, nodes.length);\n try {\n const structuralStyleFiles: StyleSourceFile[] = [];\n const filesByFamily = new Map<DuplicateFamily, FileTokens[]>();\n await Promise.all(\n nodes.map(async (node) => {\n let source: string;\n try {\n source = await readFile(path.join(rootDir, node.path), \"utf8\");\n } catch {\n // File listed in the graph but no longer readable (deleted/moved since build) — skip.\n return;\n }\n\n if (STRUCTURAL_STYLE_TYPES.has(node.type)) {\n structuralStyleFiles.push({ file: node.path, source, fileType: node.type });\n return;\n }\n\n const family = getDuplicateFamily(node.type);\n const tokens = pool\n ? await pool.run({ source, fileType: node.type, ignoreLiterals })\n : tokenize(source, node.type, ignoreLiterals);\n const fileTokens: FileTokens = { file: node.path, tokens };\n const bucket = filesByFamily.get(family);\n if (bucket) bucket.push(fileTokens);\n else filesByFamily.set(family, [fileTokens]);\n }),\n );\n\n const groups: DuplicateGroup[] = findStyleBlockDuplicates(structuralStyleFiles, minLines);\n let skippedBuckets = 0;\n\n // Token-shingle matching never crosses a family boundary (see families.ts) — each family's\n // shingle windows are hashed and chain-merged in isolation, so (e.g.) Stylus can never\n // register as a \"duplicate\" of an unrelated TS/JS/Python shape, and vice versa.\n for (const [family, fileTokens] of filesByFamily) {\n const result = findDuplicateGroups(\n fileTokens,\n windowSize,\n minLines,\n maxPunctuationRatio,\n maxBucketSize,\n );\n skippedBuckets += result.skippedBuckets;\n for (const group of result.groups) {\n groups.push({ ...group, family });\n }\n }\n\n return { groups: groups.sort((a, b) => b.lines - a.lines).slice(0, limit), skippedBuckets };\n } finally {\n if (pool) await pool.destroy();\n }\n}\n\n/**\n * @description Decides whether to spin up a worker pool for tokenizing this scan's candidate\n * files, and constructs it if so — same shape as `GraphBuilder`'s `parseFile` pool (see\n * docs/adr-010-parallel-parsing.md), reused here because tokenizing thousands of files\n * in-process can itself take long enough to matter on a large repo. Pool construction is\n * wrapped in try/catch — a spawn failure (e.g. a sandboxed environment without `worker_threads`\n * permission) falls back to synchronous in-process tokenizing for the whole scan.\n * @param option - `false` always tokenizes in-process; `true`/omitted offloads once\n * `candidateFileCount` reaches `minFiles` (default 20); an object overrides `minFiles`/\n * `maxThreads`.\n * @param candidateFileCount - Number of files this scan will tokenize, already known up front\n * (unlike `GraphBuilder`'s discovery-driven traversal), so no pre-scan walk is needed here.\n */\nfunction createTokenizingPool(\n option: ParallelTokenizingOption,\n candidateFileCount: number,\n): Piscina | null {\n if (option === false) return null;\n\n const opts = typeof option === \"object\" ? option : {};\n const minFiles = opts.minFiles ?? DEFAULT_MIN_FILES_FOR_POOL;\n if (candidateFileCount < minFiles) return null;\n\n try {\n // duplication/index.ts is bundled into dist/index.js (same tsup entry as builder.ts, which\n // relies on the same fact for parse-worker.js — see docs/adr-010-parallel-parsing.md), so\n // __dirname at runtime resolves to dist/, not dist/graph/duplication/.\n return new Piscina({\n filename: path.join(__dirname, \"duplication-worker.js\"),\n ...(opts.maxThreads !== undefined ? { maxThreads: opts.maxThreads } : {}),\n });\n } catch (err) {\n process.stderr.write(\n `\\nWarning: failed to start duplicate-detection worker pool, falling back to synchronous tokenizing: ${err}\\n`,\n );\n return null;\n }\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 { load as loadYaml } 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\n/**\n * Lock file basenames mokosh knows how to parse for version annotation. Exported so other\n * features (e.g. `findDuplicates`) can exclude these files by the same single source of truth\n * instead of re-declaring the list — a lock file's repeated JSON/YAML dependency blocks are\n * real textual repetition, but not code duplication.\n */\nexport const LOCK_FILE_NAMES: readonly string[] = [\n \"package-lock.json\",\n \"yarn.lock\",\n \"pnpm-lock.yaml\",\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 = loadYaml(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 = loadYaml(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 parsers: Record<string, (lockFilePath: string) => LockFileData> = {\n \"package-lock.json\": parsePackageLock,\n \"yarn.lock\": parseYarnLock,\n \"pnpm-lock.yaml\": parsePnpmLock,\n };\n const candidates = LOCK_FILE_NAMES.map(\n (filename) => [filename, parsers[filename] as (lockFilePath: string) => LockFileData] as const,\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","/**\n * Language-family partitioning for duplicate detection. `findDuplicates` never compares files\n * across a family boundary — see docs/adr-013-duplicate-detection-noise-reduction.md. The\n * immediate driver is CSS-family declarations: they share a small, finite vocabulary (property\n * names, common value keywords) that the shared `KEYWORDS` denylist in `tokenizer.ts` doesn't\n * cover, so unrelated style rules with the same declaration *shape* but different selectors/\n * properties/values were hashing identically to unrelated TS/JS/Python shapes too. Splitting by\n * family also lets later phases tune `windowSize`/`minLines`/vocabulary per family without one\n * family's tuning affecting another's.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One partition of `findDuplicates` matching — files in different families are never compared. */\nexport type DuplicateFamily = \"style\" | \"code\";\n\n/** CSS-family languages: small, finite declaration vocabulary compared to code identifiers. */\nconst STYLE_FILE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"css\",\n \"scss\",\n \"less\",\n \"stylus\",\n]);\n\n/**\n * @description Maps a `FileType` to the duplicate-detection family it belongs to. Everything\n * outside the CSS family (TS/JS, Python, Go, CoffeeScript, LiveScript, Lua, Gherkin, Markdown,\n * …) falls into the single `\"code\"` family — the shared `KEYWORDS` denylist in `tokenizer.ts`\n * is already oriented toward that group, so they can keep matching against each other as\n * `findDuplicates` does today.\n * @param fileType - The file's parsed language.\n * @returns The family `fileType` belongs to.\n */\nexport function getDuplicateFamily(fileType: FileType): DuplicateFamily {\n return STYLE_FILE_TYPES.has(fileType) ? \"style\" : \"code\";\n}\n","/**\n * Sliding-window (shingle) hashing over a normalized token stream, plus chain-merging of\n * consecutive matching windows into contiguous duplicate blocks, a structural-punctuation-density\n * gate that drops blocks that are mostly object/array-literal shape (e.g. MCP tool `inputSchema`\n * boilerplate) rather than substantive shared logic, and exact-occurrence clustering of pair-\n * matches into one N-occurrence group instead of reporting C(N,2) near-identical pairs for a block\n * repeated N times. Shared by every language `tokenize()` supports — the shingling step itself has\n * no language awareness at all. See docs/adr-013-duplicate-detection-noise-reduction.md for the\n * noise this addresses, and docs/adr-014-duplicate-detection-scale.md for why oversized hash\n * buckets are capped below.\n */\nimport type { DuplicateFamily } from \"./families\";\nimport type { NormalizedToken } from \"./tokenizer\";\n\n/** One file's worth of tokens, kept together so a match can be traced back to its source. */\nexport interface FileTokens {\n file: string;\n tokens: NormalizedToken[];\n}\n\nexport interface DuplicateOccurrence {\n file: string;\n startLine: number;\n endLine: number;\n}\n\nexport interface DuplicateGroup {\n /** Every location sharing this duplicated block (two or more) — locations that pairwise\n * chain-match are clustered into one group instead of being reported once per pair, so a\n * block repeated N times produces one N-occurrence group, not C(N,2) near-identical ones. */\n occurrences: DuplicateOccurrence[];\n /** Line span of the largest single pairwise match clustered into this group (each pair's own\n * span is the shorter of its two occurrences) — the best-verified size for this block, not an\n * average or a value shrunk by a more weakly-matching cluster member. */\n lines: number;\n /** Token-window length backing this block, after chain-merging adjacent windows. */\n tokens: number;\n /** Which language family both occurrences belong to (set by `findDuplicates`, which never\n * matches across families — see docs/adr-013-duplicate-detection-noise-reduction.md).\n * Absent when called directly with a token stream that isn't family-scoped, e.g. in tests. */\n family?: DuplicateFamily | undefined;\n}\n\ninterface Location {\n file: string;\n windowIndex: number;\n}\n\n/**\n * @description A small non-cryptographic string hash (djb2), good enough to bucket identical\n * token windows together for a heuristic duplicate finder — collision risk is negligible at\n * the window sizes and file counts this operates on, and any collision only costs an extra\n * comparison, never a wrong report, since chains are still verified position-by-position.\n * @param text - The joined-token window text to hash.\n * @returns A hash string safe to use as a `Map` key.\n */\nfunction hashWindow(text: string): string {\n let hash = 5381;\n for (let i = 0; i < text.length; i++) {\n hash = (hash * 33 + text.charCodeAt(i)) >>> 0;\n }\n return hash.toString(36);\n}\n\n/**\n * @description Computes every `windowSize`-token window's hash for one file's token stream.\n * @param tokens - The file's normalized tokens.\n * @param windowSize - Number of tokens per shingle window.\n * @returns Hash per window index, aligned so `hashes[i]` is the window starting at token `i`.\n */\nfunction hashWindows(tokens: NormalizedToken[], windowSize: number): string[] {\n const hashes: string[] = [];\n for (let i = 0; i <= tokens.length - windowSize; i++) {\n hashes.push(\n hashWindow(\n tokens\n .slice(i, i + windowSize)\n .map((t) => t.text)\n .join(\" \"),\n ),\n );\n }\n return hashes;\n}\n\n/**\n * @description Extends a matching window pair forward for as long as the next window in both\n * files keeps hashing identically, turning one shingle match into the longest contiguous\n * duplicate block it's part of.\n * @param hashesByFile - Precomputed per-window hashes, one array per file.\n * @param a - Starting location in the first file.\n * @param b - Starting location in the second file.\n * @returns How many additional windows (beyond the first) also match.\n */\nfunction extendChain(hashesByFile: Map<string, string[]>, a: Location, b: Location): number {\n const hashesA = hashesByFile.get(a.file);\n const hashesB = hashesByFile.get(b.file);\n if (!hashesA || !hashesB) return 0;\n let extra = 0;\n while (\n a.windowIndex + extra + 1 < hashesA.length &&\n b.windowIndex + extra + 1 < hashesB.length &&\n hashesA[a.windowIndex + extra + 1] === hashesB[b.windowIndex + extra + 1]\n ) {\n extra++;\n }\n return extra;\n}\n\n/**\n * @description Whether `(a, b)` is the start of a duplicate chain rather than the middle of\n * one already found from an earlier starting position — true when the previous window pair\n * did *not* also match, so each contiguous run is reported exactly once, from its earliest\n * position.\n */\nfunction isChainStart(hashesByFile: Map<string, string[]>, a: Location, b: Location): boolean {\n if (a.windowIndex === 0 || b.windowIndex === 0) return true;\n const hashesA = hashesByFile.get(a.file);\n const hashesB = hashesByFile.get(b.file);\n if (!hashesA || !hashesB) return true;\n return hashesA[a.windowIndex - 1] !== hashesB[b.windowIndex - 1];\n}\n\n/** Plain union-find over opaque string keys, path-compressed. Used below to cluster pair-matches\n * that share an *exact* occurrence — same file and identical start/end line — into one group,\n * rather than one report per pair. Deliberately not transitive over \"chain-matches some other\n * location that chain-matches a third\" in general: two different (file, start) locations can\n * each independently chain-match a shared window to very different lengths (e.g. one long\n * genuine clone plus, separately, several short internally-repeated sub-patterns within it), and\n * merging on that looser basis would force every member down to the shortest edge's length,\n * silently discarding the strongest match. Requiring an exact shared span sidesteps that: it\n * only fires when two independently-computed pair-matches agree on the very same block for one\n * of their two sides (the same repeated-boilerplate location matched against several others),\n * which is exactly the pair-explosion case this clustering targets. */\nclass ExactOccurrenceUnionFind {\n private readonly parent = new Map<string, string>();\n\n private find(key: string): string {\n if (!this.parent.has(key)) this.parent.set(key, key);\n let root = key;\n while (this.parent.get(root) !== root) root = this.parent.get(root) as string;\n let cur = key;\n while (cur !== root) {\n const next = this.parent.get(cur) as string;\n this.parent.set(cur, root);\n cur = next;\n }\n return root;\n }\n\n union(keyA: string, keyB: string): void {\n const rootA = this.find(keyA);\n const rootB = this.find(keyB);\n if (rootA !== rootB) this.parent.set(rootA, rootB);\n }\n\n root(key: string): string {\n return this.find(key);\n }\n}\n\n/** `\"<file>#<startLine>#<endLine>\"` — exact-span identity for one reported occurrence. */\nfunction occurrenceKey(occ: DuplicateOccurrence): string {\n return `${occ.file}#${occ.startLine}#${occ.endLine}`;\n}\n\n/** Object/array-literal structural punctuation — braces, colons, commas, brackets. Deliberately\n * narrower than \"all punctuation\": parens and semicolons are just as common in ordinary control\n * flow and would blur the signal below. */\nconst STRUCTURAL_PUNCTUATION = new Set([\"{\", \"}\", \":\", \",\", \"[\", \"]\"]);\n\n/**\n * @description The fraction of a token window's texts that are object/array-literal structural\n * punctuation (`{ } : , [ ]`), used to gate out blocks that are mostly object-literal/schema\n * shape — e.g. MCP tool `inputSchema` boilerplate repeated across unrelated tool definitions —\n * rather than substantive shared logic. An earlier version of this gate measured *identifier*\n * diversity instead (rejecting windows where too few distinct token texts appear), but that\n * measure doesn't distinguish schema boilerplate from genuine duplication: table-driven test\n * fixtures (deliberately mirrored across, e.g., parallel Go/Python test suites) are just as\n * token-repetitive as schema boilerplate, so it silently discarded real matches. Punctuation\n * density instead targets the *shape* that's actually distinctive about object/schema literals\n * — dense braces/colons/commas relative to keywords and content — which genuine control-flow\n * logic and repeated test-fixture data don't share, verified empirically against mokosh's own\n * codebase (~0.60–0.74 for schema boilerplate vs. ~0.07–0.43 for real duplicated logic,\n * including table-driven test fixtures). See docs/adr-013-duplicate-detection-noise-reduction.md.\n * @param tokens - The full token stream a window is sliced from.\n * @param start - Window start index.\n * @param length - Window length in tokens.\n * @returns `structuralPunctuationTokens / length`, in `[0, 1]`.\n */\nfunction structuralPunctuationRatio(\n tokens: NormalizedToken[],\n start: number,\n length: number,\n): number {\n let count = 0;\n for (let i = start; i < start + length; i++) {\n if (STRUCTURAL_PUNCTUATION.has((tokens[i] as NormalizedToken).text)) count++;\n }\n return count / length;\n}\n\ninterface PairMatch {\n occA: DuplicateOccurrence;\n occB: DuplicateOccurrence;\n lines: number;\n tokens: number;\n}\n\n/**\n * Default cap on how many locations a single hash bucket may hold before its pairwise comparison\n * is skipped outright. Comparison cost per bucket is O(k²) in the bucket's location count `k`; on\n * a large enough repo, a single ubiquitous window (a common import line, a boilerplate header —\n * exactly the kind of token pattern `maxPunctuationRatio` already exists to filter, but *before*\n * chain-extension has run) can produce a bucket with thousands of locations, and just a handful of\n * those buckets is enough to blow the total comparison count past what a single MCP call can\n * finish inside its client's response timeout. Skipped buckets are almost always this kind of\n * high-frequency boilerplate rather than a single large genuine clone (a real N-way clone repeated\n * this often would itself be unusual), so the cap trades a small chance of under-reporting\n * extremely widespread duplication for the pipeline finishing at all. See\n * docs/adr-014-duplicate-detection-scale.md.\n */\nconst DEFAULT_MAX_BUCKET_SIZE = 400;\n\n/**\n * @description Builds duplicate groups from a set of files' token streams: hashes every\n * `windowSize`-token sliding window per file, pairs up locations sharing a hash, chain-merges\n * each chain-starting pair forward into the longest contiguous block it starts (exactly as a\n * pairwise-only detector would), drops pairs whose block is mostly object/array-literal\n * structural punctuation rather than substantive content, then merges pair-matches that share\n * an exact occurrence (same file, same start/end line on one side) into a single group — so a\n * block repeated N times across the project is reported once, with N occurrences, instead of\n * once per pair, without shortening any individual match to accommodate an unrelated one.\n * @param files - Per-file normalized token streams (already comment-stripped and identifier/\n * literal-normalized by `tokenize`).\n * @param windowSize - Shingle window length in tokens — the smallest duplicate this can find.\n * @param minLines - Minimum merged block size (in source lines) to report; smaller matches\n * are almost always incidental (short getters, boilerplate imports) rather than real\n * duplication.\n * @param maxPunctuationRatio - Maximum fraction of a block's window that may be object/array-\n * literal structural punctuation (default 0.5) — see {@link structuralPunctuationRatio}. Set\n * to 1 to disable the gate.\n * @param maxBucketSize - Skip a hash bucket's O(k²) pairwise comparison entirely once it holds\n * more than this many locations (default {@link DEFAULT_MAX_BUCKET_SIZE}) — see the constant's\n * doc comment. Set to `Infinity` to disable.\n * @returns Duplicate blocks, each with two or more occurrences, sorted largest-first, plus\n * `skippedBuckets` — how many hash buckets were dropped for exceeding `maxBucketSize` (0 when\n * the cap never triggered), so callers can surface that results may be incomplete.\n */\nexport function findDuplicateGroups(\n files: FileTokens[],\n windowSize: number,\n minLines: number,\n maxPunctuationRatio = 0.5,\n maxBucketSize = DEFAULT_MAX_BUCKET_SIZE,\n): { groups: DuplicateGroup[]; skippedBuckets: number } {\n const hashesByFile = new Map<string, string[]>();\n const tokensByFile = new Map<string, NormalizedToken[]>();\n const buckets = new Map<string, Location[]>();\n\n for (const { file, tokens } of files) {\n if (tokens.length < windowSize) continue;\n tokensByFile.set(file, tokens);\n const hashes = hashWindows(tokens, windowSize);\n hashesByFile.set(file, hashes);\n for (let i = 0; i < hashes.length; i++) {\n const loc: Location = { file, windowIndex: i };\n const bucket = buckets.get(hashes[i] as string);\n if (bucket) bucket.push(loc);\n else buckets.set(hashes[i] as string, [loc]);\n }\n }\n\n const pairMatches: PairMatch[] = [];\n let skippedBuckets = 0;\n\n for (const locations of buckets.values()) {\n if (locations.length < 2) continue;\n if (locations.length > maxBucketSize) {\n skippedBuckets++;\n continue;\n }\n for (let i = 0; i < locations.length; i++) {\n for (let j = i + 1; j < locations.length; j++) {\n const a = locations[i] as Location;\n const b = locations[j] as Location;\n if (a.file === b.file && a.windowIndex === b.windowIndex) continue;\n if (!isChainStart(hashesByFile, a, b)) continue;\n\n const extra = extendChain(hashesByFile, a, b);\n const length = windowSize + extra;\n\n const tokensA = tokensByFile.get(a.file) as NormalizedToken[];\n if (structuralPunctuationRatio(tokensA, a.windowIndex, length) > maxPunctuationRatio)\n continue;\n\n // Same-file self-overlap isn't a real duplicate (it's just the same span vs itself).\n if (a.file === b.file) {\n const [lo, hi] = a.windowIndex < b.windowIndex ? [a, b] : [b, a];\n if (lo.windowIndex + length > hi.windowIndex) continue;\n }\n\n const tokensB = tokensByFile.get(b.file) as NormalizedToken[];\n const occA: DuplicateOccurrence = {\n file: a.file,\n startLine: (tokensA[a.windowIndex] as NormalizedToken).line,\n endLine: (tokensA[a.windowIndex + length - 1] as NormalizedToken).line,\n };\n const occB: DuplicateOccurrence = {\n file: b.file,\n startLine: (tokensB[b.windowIndex] as NormalizedToken).line,\n endLine: (tokensB[b.windowIndex + length - 1] as NormalizedToken).line,\n };\n const lines = Math.min(\n occA.endLine - occA.startLine + 1,\n occB.endLine - occB.startLine + 1,\n );\n if (lines < minLines) continue;\n\n pairMatches.push({ occA, occB, lines, tokens: length });\n }\n }\n }\n\n const clusters = new ExactOccurrenceUnionFind();\n const occurrenceByKey = new Map<string, DuplicateOccurrence>();\n for (const { occA, occB } of pairMatches) {\n const keyA = occurrenceKey(occA);\n const keyB = occurrenceKey(occB);\n occurrenceByKey.set(keyA, occA);\n occurrenceByKey.set(keyB, occB);\n clusters.union(keyA, keyB);\n }\n\n const keysByRoot = new Map<string, Set<string>>();\n for (const key of occurrenceByKey.keys()) {\n const root = clusters.root(key);\n const keys = keysByRoot.get(root);\n if (keys) keys.add(key);\n else keysByRoot.set(root, new Set([key]));\n }\n\n const groups: DuplicateGroup[] = [];\n for (const keys of keysByRoot.values()) {\n if (keys.size < 2) continue;\n const occurrences = [...keys].map((key) => occurrenceByKey.get(key) as DuplicateOccurrence);\n // Best (largest) individual pairwise match fully contained in this cluster — reported as the\n // group's size, rather than a value shrunk by an unrelated, more weakly-matching member.\n let lines = 0;\n let tokens = windowSize;\n for (const pm of pairMatches) {\n if (!keys.has(occurrenceKey(pm.occA)) || !keys.has(occurrenceKey(pm.occB))) continue;\n if (pm.lines > lines) {\n lines = pm.lines;\n tokens = pm.tokens;\n }\n }\n groups.push({ occurrences, lines, tokens });\n }\n\n return { groups: groups.sort((a, b) => b.lines - a.lines), skippedBuckets };\n}\n","/** Parses CSS and Less files using PostCSS to extract @import edges, plus root-level Less variable exports. */\nimport postcss from \"postcss\";\nimport * as less from \"postcss-less\";\nimport type { ExportedSymbol, ImportEdge, StructuredTag } from \"../../types/node\";\n\nconst lessParser = 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 Walks the root-level nodes of a parsed Less AST and collects `@variable: value;`\n * declarations as the file's exported surface. `postcss-less` represents these as `atrule` nodes\n * carrying a non-standard `value` property (unlike real at-rules such as `@media`/`@import`, which\n * never have one) — that's the reliable signal used to tell a variable apart from a directive.\n * Mixin *definitions* (`.name(...) { }`) are deliberately not extracted — distinguishing a definition\n * from an ordinary rule with a parenthesized selector, or from a mixin *call* (`.name();`), needs\n * deeper Less semantics than a straight AST walk gives for free.\n * @param {postcss.Root} root - The parsed Less AST\n * @returns {{ exports: ExportedSymbol[]; tags: StructuredTag[] }} Root-level Less variable exports and their matching declaration tags\n */\nfunction extractLessVariableExports(root: postcss.Root): {\n exports: ExportedSymbol[];\n tags: StructuredTag[];\n} {\n const exports: ExportedSymbol[] = [];\n const tags: StructuredTag[] = [];\n\n for (const node of root.nodes ?? []) {\n if (node.type !== \"atrule\") continue;\n const { value } = node as unknown as { value?: string };\n if (value === undefined) continue;\n const name = node.name;\n if (!name) continue;\n exports.push({ name });\n tags.push({ name, kind: \"variable\" });\n }\n\n return { exports, tags };\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 * Also extracts root-level `@variable` declarations as the file's exports and matching declaration tags.\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; exports: ExportedSymbol[]; tags: StructuredTag[] }} The collected import edges, the PostCSS root (may be empty on parse failure), and the file's exported surface\n */\nexport function parseLessContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root; exports: ExportedSymbol[]; tags: StructuredTag[] } {\n try {\n const root = lessParser.parse(content);\n const { exports, tags } = extractLessVariableExports(root);\n return { imports: collectEdgesFromRoot(root, filePath), root, exports, tags };\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 {\n imports: regexFallbackImports(content, filePath),\n root: postcss.parse(\"\"),\n exports: [],\n tags: [],\n };\n }\n}\n","/** Parses SCSS/Sass files using postcss-scss to extract @use, @forward, and @import edges, plus root-level variable/mixin/function exports. */\nimport type postcss from \"postcss\";\nimport { parse as scssParse } from \"postcss-scss\";\nimport type { ExportedSymbol, ImportEdge, StructuredTag } from \"../../types/node\";\n\n/**\n * @description Returns true when a Sass identifier follows the module-privacy convention\n * (leading `_` or `-`), meaning it is never visible outside the file via `@use` and so is\n * not part of the module's exported surface.\n * @param {string} name - The variable, mixin, or function name (without its `$` sigil, if any)\n * @returns {boolean} `true` for private names\n */\nfunction isScssPrivateName(name: string): boolean {\n return name.startsWith(\"_\") || name.startsWith(\"-\");\n}\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 Walks the root-level nodes of a parsed SCSS AST and collects `$variable` declarations\n * and `@mixin`/`@function` at-rules as the file's exported surface, mirroring how `export` works in TS.\n * Skips Sass-private names (leading `_`/`-`), which are never visible outside the file via `@use`,\n * and skips anything not declared directly at the root (rule- or mixin-body-scoped declarations are local).\n * @param {postcss.Root} root - The parsed SCSS AST\n * @returns {{ exports: ExportedSymbol[]; tags: StructuredTag[] }} Root-level variable/mixin/function exports and their matching declaration tags\n */\nfunction extractScssExports(root: postcss.Root): {\n exports: ExportedSymbol[];\n tags: StructuredTag[];\n} {\n const exports: ExportedSymbol[] = [];\n const tags: StructuredTag[] = [];\n\n for (const node of root.nodes ?? []) {\n if (node.type === \"decl\" && node.prop.startsWith(\"$\")) {\n const name = node.prop.slice(1);\n if (!name || isScssPrivateName(name)) continue;\n exports.push({ name });\n tags.push({ name, kind: \"variable\" });\n continue;\n }\n\n if (node.type === \"atrule\" && (node.name === \"mixin\" || node.name === \"function\")) {\n const match = node.params.match(/^([\\w-]+)/);\n const name = match?.[1];\n if (!name || isScssPrivateName(name)) continue;\n const signature = node.params.trim();\n exports.push(signature.includes(\"(\") ? { name, signature } : { name });\n tags.push({ name, kind: \"function\" });\n }\n }\n\n return { exports, tags };\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 * Also extracts root-level `$variable`/`@mixin`/`@function` declarations as the file's exports and matching declaration tags.\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; exports: ExportedSymbol[]; tags: StructuredTag[] }} The collected import edges, the PostCSS root (used for barrel detection), and the file's exported surface\n */\nexport function parseScssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root; exports: ExportedSymbol[]; tags: StructuredTag[] } {\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 const { exports, tags } = extractScssExports(root);\n return { imports, root, exports, tags };\n}\n","/**\n * Structural (not token-shingled) duplicate detection for CSS-family \"block definitions\" — a\n * rule's declaration *body* (its literal, ordered `property: value` pairs), independent of the\n * rule's own selector. Reuses the PostCSS ASTs the CSS/Less/SCSS parsers already build\n * (`src/parser/style/*`) instead of a generic token window, so matching happens on the actual\n * declaration content rather than a normalized token shape — a `display: flex` rule and a\n * `display: block` rule can never register as a duplicate just because both collapse to the same\n * `ID : ID ;` shape, which was the false-positive mode the token-shingle path had for CSS. See\n * docs/adr-013-duplicate-detection-noise-reduction.md.\n *\n * Stylus is deliberately excluded: it has no PostCSS-compatible AST available in this codebase\n * (`src/parser/style/stylus.ts` only regex-extracts imports), so Stylus files still fall back to\n * the generic token-shingle pipeline in `tokenizer.ts`/`shingle.ts`.\n */\nimport type postcss from \"postcss\";\nimport { parseCssContent, parseLessContent } from \"../../parser/style/css\";\nimport { parseScssContent } from \"../../parser/style/scss\";\nimport type { FileType } from \"../../types/parse\";\nimport type { DuplicateGroup, DuplicateOccurrence } from \"./shingle\";\n\nexport interface StyleSourceFile {\n file: string;\n source: string;\n fileType: FileType;\n}\n\ninterface RuleBlock {\n file: string;\n startLine: number;\n endLine: number;\n lines: number;\n signature: string;\n declCount: number;\n}\n\n/**\n * @description Collapses a declaration value to a comparable form without touching its literal\n * content — only incidental whitespace differences (copy-pasted code reformatted by a linter,\n * say) are normalized away.\n * @param value - A declaration's raw PostCSS `value`.\n * @returns The value trimmed and with internal whitespace runs collapsed to one space.\n */\nfunction normalizeValue(value: string): string {\n return value.trim().replace(/\\s+/g, \" \");\n}\n\n/**\n * @description Parses one style file into a PostCSS AST using the same dialect-specific parser\n * `src/parser/style/index.ts` dispatches to, so block extraction sees exactly what the graph\n * builder saw. Returns `undefined` for Stylus (no shared AST) or on a parse failure.\n * @param file - The file to parse; `fileType` selects the CSS/Less/SCSS dialect.\n * @returns The parsed PostCSS root, or `undefined` when structural parsing isn't available.\n */\nfunction rootFor(file: StyleSourceFile): postcss.Root | undefined {\n try {\n if (file.fileType === \"scss\") return parseScssContent(file.source, file.file).root;\n if (file.fileType === \"less\") return parseLessContent(file.source, file.file).root;\n if (file.fileType === \"css\") return parseCssContent(file.source, file.file).root;\n return undefined;\n } catch {\n // Malformed input the dialect parser couldn't recover from — skip rather than throw, same\n // trade-off the token-shingle path makes for unreadable files.\n return undefined;\n }\n}\n\n/**\n * @description Walks a style file's AST and extracts every rule's declaration body as a\n * comparable block, regardless of nesting depth (`@media`-wrapped and SCSS-nested rules are\n * walked the same as top-level ones).\n * @param file - The file to extract rule blocks from.\n * @returns One `RuleBlock` per rule that has at least one declaration.\n */\nfunction collectRuleBlocks(file: StyleSourceFile): RuleBlock[] {\n const root = rootFor(file);\n if (!root) return [];\n const blocks: RuleBlock[] = [];\n\n root.walkRules((rule) => {\n const decls = rule.nodes?.filter((node) => node.type === \"decl\") ?? [];\n if (decls.length === 0) return;\n\n const firstDecl = decls[0];\n const lastDecl = decls[decls.length - 1];\n const startLine = rule.source?.start?.line ?? firstDecl?.source?.start?.line;\n const endLine = lastDecl?.source?.end?.line ?? lastDecl?.source?.start?.line ?? startLine;\n if (startLine === undefined || endLine === undefined) return;\n\n const signature = decls\n .map((decl) => `${decl.prop.trim().toLowerCase()}:${normalizeValue(decl.value)}`)\n .join(\";\");\n\n blocks.push({\n file: file.file,\n startLine,\n endLine,\n lines: endLine - startLine + 1,\n signature,\n declCount: decls.length,\n });\n });\n\n return blocks;\n}\n\n/**\n * @description Finds duplicated CSS/Less/SCSS rule *bodies* — the literal, ordered set of\n * `property: value` declarations a rule contains — independent of the rule's own selector.\n * Two rules with different selectors but an identical declaration list are reported (a real\n * \"should this become a shared class or mixin\" signal); two rules that merely share\n * declaration *shape* but differ in property or value never are, unlike generic token-shingle\n * matching.\n * @param files - Style-family source files to scan. Stylus entries are silently skipped —\n * callers should route them through the token-shingle path instead.\n * @param minLines - Minimum line span a duplicated block must cover to be reported.\n * @param minDeclarations - Minimum number of declarations a duplicated block must contain, so a\n * single shared one-liner (e.g. `display: none;` alone) doesn't count as duplication.\n * @returns Duplicate blocks, each a pair of occurrences, sorted largest-first. Every group is\n * tagged `family: \"style\"`.\n */\nexport function findStyleBlockDuplicates(\n files: StyleSourceFile[],\n minLines: number,\n minDeclarations = 2,\n): DuplicateGroup[] {\n const buckets = new Map<string, RuleBlock[]>();\n for (const file of files) {\n for (const block of collectRuleBlocks(file)) {\n if (block.declCount < minDeclarations || block.lines < minLines) continue;\n const bucket = buckets.get(block.signature);\n if (bucket) bucket.push(block);\n else buckets.set(block.signature, [block]);\n }\n }\n\n const groups: DuplicateGroup[] = [];\n for (const bucket of buckets.values()) {\n for (let i = 0; i < bucket.length; i++) {\n for (let j = i + 1; j < bucket.length; j++) {\n const a = bucket[i] as RuleBlock;\n const b = bucket[j] as RuleBlock;\n // Same-file self-match on the exact same span isn't a real duplicate.\n if (a.file === b.file && a.startLine === b.startLine && a.endLine === b.endLine) continue;\n\n const occA: DuplicateOccurrence = {\n file: a.file,\n startLine: a.startLine,\n endLine: a.endLine,\n };\n const occB: DuplicateOccurrence = {\n file: b.file,\n startLine: b.startLine,\n endLine: b.endLine,\n };\n groups.push({\n occurrences: [occA, occB],\n lines: Math.min(a.lines, b.lines),\n tokens: a.declCount,\n family: \"style\",\n });\n }\n }\n }\n\n return groups.sort((a, b) => b.lines - a.lines);\n}\n","/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, CoffeeScript,\n * LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: identifiers always collapse to a single placeholder (so renamed-variable clones\n * still hash identically), and literals optionally collapse too (`ignoreLiterals`, default\n * on) so only structural shape — not the specific values used — drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment-stripping rule; unrecognised types skip stripping.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripComments(source, COMMENT_SYNTAX[fileType]);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\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","/** Single source of truth for which languages' parsers track which precision-relevant data. */\nimport type { FileType } from \"../types/parse\";\nimport type { Graph } from \"./model\";\n\n/** File types whose parser ever populates `FileNode.exports`. */\nexport const EXPORT_TRACKING_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"python\",\n \"go\",\n \"scss\",\n \"less\",\n \"lua\",\n \"coffeescript\",\n]);\n\n/** File types whose parser records which named symbols each import edge pulls in (`ImportEdge.symbols`). */\nexport const IMPORT_SYMBOL_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"python\",\n]);\n\n/** File types whose parser records function-level call edges (`FileNode.callEdges`). */\nexport const CALL_EDGE_TYPES: ReadonlySet<FileType> = new Set<FileType>([\n \"typescript\",\n \"javascript\",\n \"go\",\n \"python\",\n]);\n\nexport interface LanguageCoverage {\n type: FileType;\n fileCount: number;\n exportsTracked: boolean;\n importSymbolsTracked: boolean;\n callEdgesTracked: boolean;\n}\n\n/**\n * @description Reports which precision-relevant data mokosh actually tracks for each language\n * present in `graph` — so a caller can tell upfront whether tools like `find_symbol` or\n * `get_call_graph` will give call-level precision, degrade to import-level, or find nothing\n * at all for a given file, before running a query and being surprised by the result.\n * @param graph - The graph to summarize.\n * @returns One entry per `FileType` actually present in `graph`, sorted by file count\n * descending. Languages with zero files in this graph are omitted.\n */\nexport function getLanguageCoverage(graph: Graph): LanguageCoverage[] {\n const counts = new Map<FileType, number>();\n for (const node of graph.nodes.values()) {\n counts.set(node.type, (counts.get(node.type) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .map(([type, fileCount]) => ({\n type,\n fileCount,\n exportsTracked: EXPORT_TRACKING_TYPES.has(type),\n importSymbolsTracked: IMPORT_SYMBOL_TYPES.has(type),\n callEdgesTracked: CALL_EDGE_TYPES.has(type),\n }))\n .sort((a, b) => b.fileCount - a.fileCount);\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","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","/** Pure graph query/shaping functions shared by the MCP handlers and the CLI, so both surfaces return identical JSON shapes. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport type { FileNode } from \"../types/node\";\nimport type { Graph } from \"./model\";\nimport { SymbolTraversalContext } from \"./symbol-traversal\";\nimport type { WorkspaceGraph } from \"./workspace-model\";\n\nexport interface PathWithSymbols {\n path: string;\n symbols?: string[];\n}\n\nexport interface NodeMeta {\n category: FileNode[\"category\"];\n exports: string[];\n}\n\n/**\n * @description Looks up a node's category and export names, for augmenting traversal\n * results with `withMeta` so the AI doesn't need a follow-up `query` call.\n * @param graph - The graph to look up in.\n * @param path - Project-relative path of the node.\n * @returns `{ category, exports }` for the node, or undefined if `path` isn't in the graph.\n */\nexport function getNodeMeta(graph: Graph, path: string): NodeMeta | undefined {\n const node = graph.nodes.get(path);\n if (!node) return undefined;\n return { category: node.category, exports: node.exports.map((exportedSym) => exportedSym.name) };\n}\n\n/**\n * @description Outgoing traversal from `file` — all files it imports, up to `depth` hops.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the starting node.\n * @param depth - Max traversal depth (default 1 = immediate imports only).\n * @returns Reachable imported paths, each with the symbols imported from it when known.\n */\nexport function getDependencies(graph: Graph, file: string, depth = 1): PathWithSymbols[] {\n const deps: PathWithSymbols[] = [];\n graph.traverse(\n file,\n (node, _depth, parentPath) => {\n if (node.path === file) return true;\n const edge = parentPath\n ? graph.nodes.get(parentPath)?.imports.find((importEdge) => importEdge.toPath === node.path)\n : undefined;\n deps.push({ path: node.path, ...(edge?.symbols ? { symbols: edge.symbols } : {}) });\n return true;\n },\n { direction: \"outgoing\", maxDepth: depth },\n );\n return deps;\n}\n\n/**\n * @description Incoming one-hop traversal — files that directly import `file`.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the target node.\n * @returns Direct importers, each with the symbols they import from `file` when known.\n */\nexport function getDependents(graph: Graph, file: string): PathWithSymbols[] {\n const dependents: PathWithSymbols[] = [];\n graph.traverse(\n file,\n (node) => {\n if (node.path === file) return true;\n const edge = node.imports.find((importEdge) => importEdge.toPath === file);\n dependents.push({ path: node.path, ...(edge?.symbols ? { symbols: edge.symbols } : {}) });\n return true;\n },\n { direction: \"incoming\", maxDepth: 1 },\n );\n return dependents;\n}\n\nexport interface GetAffectedOptions {\n testsOnly?: boolean | undefined;\n changedSymbols?: string[] | undefined;\n}\n\n/**\n * @description Full incoming traversal from `file` upward — every file transitively affected if `file` changes.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the changed node.\n * @param options - `testsOnly` restricts results to test/spec files; `changedSymbols` restricts propagation to files that actually import those symbols.\n * @returns Project-relative paths of all transitively impacted files.\n */\nexport function getAffected(\n graph: Graph,\n file: string,\n options: GetAffectedOptions = {},\n): string[] {\n const { testsOnly = false, changedSymbols } = options;\n const ctx = changedSymbols ? new SymbolTraversalContext(file, changedSymbols) : null;\n const affected: string[] = [];\n graph.traverse(\n file,\n (node, _depth, parentPath) => {\n if (node.path === file) return true;\n if (ctx && parentPath && !ctx.updateAffectedSymbols(node, parentPath)) return false;\n const isTest = node.category === \"test\" || node.tags.some((tag) => tag.name === \"test\");\n if (!testsOnly || isTest) affected.push(node.path);\n return true;\n },\n { direction: \"incoming\" },\n );\n return affected;\n}\n\nexport interface CallerEntry {\n file: string;\n edges?: Array<{ from: string; to: string }>;\n}\n\nexport interface GetCallersOptions {\n depth?: number | undefined;\n withEdgeDetail?: boolean | undefined;\n}\n\n/**\n * @description Incoming call-edge traversal — files whose exported functions call into `file`.\n * More precise than `getAffected` because it follows runtime call edges rather than all import edges.\n * @param graph - The graph to traverse.\n * @param file - Project-relative path of the target node.\n * @param options - `depth` caps traversal hops (default 1); `withEdgeDetail` adds from/to function names per edge.\n * @returns Callers, each optionally including edge detail.\n */\nexport function getCallers(\n graph: Graph,\n file: string,\n options: GetCallersOptions = {},\n): CallerEntry[] {\n const { depth = 1, withEdgeDetail = false } = options;\n const callers: CallerEntry[] = [];\n graph.traverseCalls(\n file,\n (node) => {\n if (node.path === file) return true;\n const entry: CallerEntry = { file: node.path };\n if (withEdgeDetail) {\n entry.edges = (node.callEdges ?? [])\n .filter((callEdge) => callEdge.toFile === file)\n .map((callEdge) => ({ from: callEdge.from, to: callEdge.to }));\n }\n callers.push(entry);\n return true;\n },\n { direction: \"incoming\", maxDepth: depth },\n );\n return callers;\n}\n\nexport interface ComplexFunctionEntry {\n file: string;\n name: string;\n line: number;\n complexity: number;\n cognitiveComplexity: number;\n}\n\nexport interface FindComplexFunctionsOptions {\n metric?: \"cognitiveComplexity\" | \"complexity\" | undefined;\n threshold?: number | undefined;\n limit?: number | undefined;\n}\n\n/**\n * @description Scans every file's per-function complexity breakdown and returns functions/methods\n * at or above the given threshold, sorted worst-first. Populated for TypeScript/JavaScript, Go,\n * and Python.\n * @param graph - The graph to scan.\n * @param options - `metric` picks which score to threshold/sort on (default `cognitiveComplexity`); `threshold` is the minimum score to include (default 10); `limit` caps the results (default 20).\n * @returns Matching functions, sorted worst-first.\n */\nexport function findComplexFunctions(\n graph: Graph,\n options: FindComplexFunctionsOptions = {},\n): ComplexFunctionEntry[] {\n const { metric = \"cognitiveComplexity\", threshold = 10, limit = 20 } = options;\n return [...graph.nodes.values()]\n .flatMap((node) =>\n (node.functions ?? [])\n .filter((fn) => fn[metric] >= threshold)\n .map((fn) => ({\n file: node.path,\n name: fn.name,\n line: fn.line,\n complexity: fn.complexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n })),\n )\n .sort((a, b) => b[metric] - a[metric])\n .slice(0, limit);\n}\n\nexport interface SlimNode {\n path: string;\n type: FileNode[\"type\"];\n category: FileNode[\"category\"];\n exports: string[];\n tags: string[];\n importsFiles: string[];\n description?: string;\n testedBy?: string[];\n coveragePct?: number;\n avgExportUsage?: number;\n maxExportUsage?: number;\n}\n\nexport interface SlimSerializedGraph {\n nodes: SlimNode[];\n cycles: string[][] | undefined;\n}\n\n/**\n * @description Strips a serialized graph down to a compact response: export names, meaningful tags,\n * and a flat importsFiles path list — no edge objects, no mtime/size.\n * @param filtered - A `SerializedGraph` (typically the output of `filterGraph`) to compact.\n * @returns The slim node list plus cycle info.\n */\nexport function slimSerialize(filtered: SerializedGraph): SlimSerializedGraph {\n const slimNodes = filtered.nodes.map((node) => ({\n path: node.path,\n type: node.type,\n category: node.category,\n exports: node.exports.map((exportedSym) => exportedSym.name),\n tags: node.tags\n .filter((tag) => tag.kind === \"comment-marker\" || tag.kind === \"import\")\n .map((tag) => tag.name),\n importsFiles: node.imports\n .filter((imp) => !imp.isExternal && imp.toPath)\n .map((imp) => imp.toPath as string),\n ...(node.description !== undefined && { description: node.description }),\n ...(node.testedBy !== undefined && { testedBy: node.testedBy }),\n ...(node.coveragePct !== undefined && { coveragePct: node.coveragePct }),\n ...(node.avgExportUsage !== undefined && { avgExportUsage: node.avgExportUsage }),\n ...(node.maxExportUsage !== undefined && { maxExportUsage: node.maxExportUsage }),\n }));\n return { nodes: slimNodes, cycles: filtered.cycles };\n}\n\nexport interface WorkspacePackageSummary {\n name: string;\n relativeRoot: string;\n nodeCount: number;\n dependsOn: string[];\n}\n\nexport interface WorkspacePackagesSummary {\n monorepoType: string;\n packageCount: number;\n packages: WorkspacePackageSummary[];\n}\n\n/**\n * @description Summarizes every package in a workspace graph: node counts and cross-package dependencies.\n * @param wg - The workspace graph to summarize.\n * @returns Monorepo type, package count, and per-package details.\n */\nexport function summarizeWorkspacePackages(wg: WorkspaceGraph): WorkspacePackagesSummary {\n const pkgDeps = wg.getPackageDependencies();\n const packages = Array.from(wg.packages.values()).map(({ graph, pkg }) => ({\n name: pkg.name,\n relativeRoot: pkg.relativeRoot,\n nodeCount: graph.nodes.size,\n dependsOn: pkgDeps.get(pkg.name) ?? [],\n }));\n return { monorepoType: wg.type, packageCount: packages.length, packages };\n}\n\n/**\n * @description Returns `true` if at least one node in the graph has coverage data loaded.\n * @param graph - The graph to check.\n * @returns Whether any node has a defined `coveragePct`.\n */\nexport function hasCoverageData(graph: Graph): boolean {\n return [...graph.nodes.values()].some((node) => node.coveragePct !== undefined);\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","/** Symbol-name lookup across the whole graph — generalizes queryCallGraph beyond TS/JS functions. */\nimport type { NodeCategory } from \"../types/parse\";\nimport { CALL_EDGE_TYPES } from \"./language-support\";\nimport type { Graph } from \"./model\";\nimport { getDependents } from \"./queries\";\n\nexport type SymbolPrecision = \"call\" | \"file-level\";\n\nexport interface SymbolCaller {\n file: string;\n callerFunction: string;\n}\n\nexport interface SymbolImporter {\n file: string;\n symbols?: string[];\n}\n\nexport interface SymbolMatch {\n path: string;\n category: NodeCategory;\n precision: SymbolPrecision;\n callers?: SymbolCaller[];\n importers?: SymbolImporter[];\n}\n\n/**\n * @description Finds every file that exports a symbol by name, with the best available\n * usage info per match. Precision depends on what the defining file's language parser\n * tracks: TS/JS, Go, and Python get function-level callers via call edges (`\"call\"`);\n * everything else falls back to whole-file dependents (`\"file-level\"` — import-level, not\n * symbol-level, since the file might not even use this specific export). Coverage still\n * differs across the `\"call\"` languages: TS/JS tracks any directly imported symbol, Go only\n * package-qualified calls, Python only bare calls to `from <module> import <name>` symbols.\n * @param graph - The graph to search.\n * @param name - Exact export name to look up.\n * @returns One entry per file that exports `name`; empty if no file does (including\n * languages — CoffeeScript, LiveScript, Lua, Gherkin, Markdown, CSS/SCSS/Stylus — whose\n * parsers never populate `exports` at all).\n */\nexport function findSymbol(graph: Graph, name: string): SymbolMatch[] {\n const matches: SymbolMatch[] = [];\n\n for (const node of graph.nodes.values()) {\n if (!node.exports.some((exportedSym) => exportedSym.name === name)) continue;\n\n if (CALL_EDGE_TYPES.has(node.type)) {\n const callers: SymbolCaller[] = [];\n for (const other of graph.nodes.values()) {\n for (const edge of other.callEdges ?? []) {\n if (edge.to === name) callers.push({ file: other.path, callerFunction: edge.from });\n }\n }\n matches.push({ path: node.path, category: node.category, precision: \"call\", callers });\n } else {\n const importers = getDependents(graph, node.path).map((dependent) => ({\n file: dependent.path,\n ...(dependent.symbols ? { symbols: dependent.symbols } : {}),\n }));\n matches.push({\n path: node.path,\n category: node.category,\n precision: \"file-level\",\n importers,\n });\n }\n }\n\n return matches;\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 { load as loadYaml } 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 = loadYaml(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","/** WorkspaceGraph holds one per-package Graph for a monorepo and exposes cross-package blast-radius queries. */\nimport type { FileNode } from \"../types/node\";\nimport { Graph } from \"./model\";\nimport type { WorkspacePackage } from \"./workspace\";\n\n/** @description JSON-safe snapshot of a `WorkspaceGraph`, suitable for writing to disk and restoring via `WorkspaceGraph.deserialize`. */\nexport interface SerializedWorkspaceGraph {\n monorepoRoot: string;\n type: string;\n packages: Array<{\n pkg: Omit<WorkspacePackage, \"root\">;\n nodes: FileNode[];\n }>;\n}\n\n/**\n * @description Holds one per-package `Graph` for each workspace package in a monorepo.\n * Cross-package import edges are preserved inside each graph via `ImportEdge.isWorkspace`.\n * The workspace graph does not merge all nodes into one flat namespace — each package graph\n * is queried independently, with cross-package traversal handled by `getAffectedAcrossPackages`.\n */\nexport class WorkspaceGraph {\n readonly packages: Map<string, { graph: Graph; pkg: WorkspacePackage }> = new Map();\n\n /**\n * @param {string} monorepoRoot - Absolute path to the monorepo root directory.\n * @param {string} type - Primary detected monorepo tool (e.g. `\"turborepo\"`, `\"pnpm\"`), or `\"none\"`.\n */\n constructor(\n readonly monorepoRoot: string,\n readonly type: string,\n ) {}\n\n /**\n * @description Registers a package and its pre-built graph into this workspace.\n * @param {WorkspacePackage} pkg - Package metadata including name, root, and entry points.\n * @param {Graph} graph - The fully-built dependency graph for this package.\n */\n addPackage(pkg: WorkspacePackage, graph: Graph): void {\n this.packages.set(pkg.name, { graph, pkg });\n }\n\n /**\n * @description Returns the workspace package whose `relativeRoot` is a path prefix of `relPath`.\n * @param {string} relPath - A monorepo-root-relative file path to look up.\n * @returns {WorkspacePackage | undefined} The owning package, or `undefined` if none matches.\n */\n getPackageForFile(relPath: string): WorkspacePackage | undefined {\n for (const { pkg } of this.packages.values()) {\n if (relPath === pkg.relativeRoot || relPath.startsWith(`${pkg.relativeRoot}/`)) {\n return pkg;\n }\n }\n return undefined;\n }\n\n /**\n * @description Returns a map of package-level dependencies derived from workspace import edges.\n * Key: package name. Value: list of workspace package names it imports from.\n * @returns {Map<string, string[]>} Map from package name to the list of workspace packages it depends on.\n */\n getPackageDependencies(): Map<string, string[]> {\n const deps = new Map<string, string[]>();\n for (const { graph, pkg } of this.packages.values()) {\n const pkgDeps = new Set<string>();\n for (const node of graph.nodes.values()) {\n for (const imp of node.imports) {\n if (imp.isWorkspace && imp.workspacePackage) {\n pkgDeps.add(imp.workspacePackage);\n }\n }\n }\n deps.set(pkg.name, [...pkgDeps]);\n }\n return deps;\n }\n\n /**\n * @description Cross-package blast-radius analysis. Returns every file (with its package name)\n * that could be affected if the given monorepo-root-relative path changes.\n * Step 1: traverses incoming edges within the owning package graph for intra-package dependents.\n * Step 2: surfaces files in other packages that hold workspace import edges pointing at the owner.\n * @param {string} relPath - Monorepo-root-relative path of the changed file.\n * @returns {Array<{ file: string; package: string }>} Each affected file paired with its package name.\n */\n getAffectedAcrossPackages(relPath: string): Array<{ file: string; package: string }> {\n const ownerPkg = this.getPackageForFile(relPath);\n if (!ownerPkg) return [];\n\n const ownerEntry = this.packages.get(ownerPkg.name);\n if (!ownerEntry) return [];\n\n const result: Array<{ file: string; package: string }> = [];\n\n // Intra-package dependents\n ownerEntry.graph.traverse(\n relPath,\n (node) => {\n if (node.path !== relPath) result.push({ file: node.path, package: ownerPkg.name });\n return true;\n },\n { direction: \"incoming\" },\n );\n\n // Cross-package: files in other packages that hold workspace imports into ownerPkg\n for (const { graph, pkg } of this.packages.values()) {\n if (pkg.name === ownerPkg.name) continue;\n for (const node of graph.nodes.values()) {\n const hasEdge = node.imports.some(\n (imp) => imp.isWorkspace && imp.workspacePackage === ownerPkg.name,\n );\n if (hasEdge) result.push({ file: node.path, package: pkg.name });\n }\n }\n\n return result;\n }\n\n /**\n * @description Serializes the workspace graph to a plain JSON-safe object.\n * `root` is omitted from package entries as it is not needed after build time.\n * @returns {SerializedWorkspaceGraph} A JSON-serializable snapshot of the workspace graph.\n */\n serialize(): SerializedWorkspaceGraph {\n return {\n monorepoRoot: this.monorepoRoot,\n type: this.type,\n packages: Array.from(this.packages.values()).map(({ graph, pkg }) => ({\n pkg: {\n name: pkg.name,\n relativeRoot: pkg.relativeRoot,\n entryPoints: pkg.entryPoints,\n },\n nodes: Array.from(graph.nodes.values()),\n })),\n };\n }\n\n /**\n * @description Reconstructs a `WorkspaceGraph` from a serialized snapshot.\n * The `root` field on each package is set to an empty string — it is not persisted and not needed for graph traversal.\n * @param {SerializedWorkspaceGraph} data - The plain object produced by `serialize`.\n * @returns {WorkspaceGraph} A fully functional `WorkspaceGraph` instance.\n */\n static deserialize(data: SerializedWorkspaceGraph): WorkspaceGraph {\n const wg = new WorkspaceGraph(data.monorepoRoot, data.type);\n for (const { pkg, nodes } of data.packages) {\n const nodeMap = new Map(nodes.map((node) => [node.path, node]));\n const graph = new Graph(nodeMap);\n wg.packages.set(pkg.name, {\n graph,\n pkg: { ...pkg, root: \"\" }, // root not persisted; not needed post-build\n });\n }\n return wg;\n }\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/**\n * @description Matches `NodeQuery.lastAuthor` against `FileNode.lastAuthor`. Nodes with no\n * author data fail the positive form and pass the negative (`!`-prefixed) form.\n */\nexport const matchLastAuthor: NodeMatcher = (node, query) => {\n if (!query.lastAuthor) return true;\n if (query.lastAuthor.startsWith(\"!\")) return node.lastAuthor !== query.lastAuthor.slice(1);\n return node.lastAuthor !== undefined && node.lastAuthor === query.lastAuthor;\n};\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/** @description Matches `NodeQuery.isDocumented` against whether `FileNode.documentedBy` is non-empty. */\nexport const matchIsDocumented: NodeMatcher = (node, query) =>\n query.isDocumented === undefined || !!node.documentedBy?.length === query.isDocumented;\n\n/** @description Matches `NodeQuery.isStale` against whether `FileNode.staleFor` is non-empty. */\nexport const matchIsStale: NodeMatcher = (node, query) =>\n query.isStale === undefined || !!node.staleFor?.length === query.isStale;\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/**\n * @description Matches `NodeQuery.minComplexity`. Nodes with no complexity data are excluded —\n * unlike `matchMinCoverage`'s large-sentinel convention (which inadvertently makes undata'd\n * nodes pass any threshold), this uses `-Infinity` so missing data always fails a `>=` check.\n */\nexport const matchMinComplexity: NodeMatcher = (node, query) =>\n query.minComplexity === undefined || (node.complexity ?? -Infinity) >= query.minComplexity;\n\n/** @description Matches `NodeQuery.maxComplexity`. Nodes with no complexity data are included (treated as 0). */\nexport const matchMaxComplexity: NodeMatcher = (node, query) =>\n query.maxComplexity === undefined || (node.complexity ?? 0) <= query.maxComplexity;\n\n/** @description Matches `NodeQuery.minCognitiveComplexity`. Nodes with no complexity data are excluded. */\nexport const matchMinCognitiveComplexity: NodeMatcher = (node, query) =>\n query.minCognitiveComplexity === undefined ||\n (node.cognitiveComplexity ?? -Infinity) >= query.minCognitiveComplexity;\n\n/** @description Matches `NodeQuery.maxCognitiveComplexity`. Nodes with no complexity data are included (treated as 0). */\nexport const matchMaxCognitiveComplexity: NodeMatcher = (node, query) =>\n query.maxCognitiveComplexity === undefined ||\n (node.cognitiveComplexity ?? 0) <= query.maxCognitiveComplexity;\n\n/** @description Matches `NodeQuery.minCommits`. Nodes with no git-stats data are excluded. */\nexport const matchMinCommits: NodeMatcher = (node, query) =>\n query.minCommits === undefined || (node.commitCount90d ?? -Infinity) >= query.minCommits;\n\n/** @description Matches `NodeQuery.maxCommits`. Nodes with no git-stats data are included (treated as 0). */\nexport const matchMaxCommits: NodeMatcher = (node, query) =>\n query.maxCommits === undefined || (node.commitCount90d ?? 0) <= query.maxCommits;\n\n/**\n * @description Matches `NodeQuery.any` — the node passes if it satisfies at least one sub-query\n * in the OR-group, ANDed with every other top-level criterion via the rest of `NODE_MATCHERS`.\n */\nexport const matchAny: NodeMatcher = (node, query, reverseIndex) =>\n !query.any?.length || query.any.some((subQuery) => matchNode(node, subQuery, reverseIndex));\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 matchLastAuthor,\n matchPath,\n matchIsExternal,\n matchTags,\n matchAllTags,\n matchImportsFile,\n matchImportedBy,\n matchMinImports,\n matchMaxImports,\n matchMinSize,\n matchMaxSize,\n matchHasDocstring,\n matchIsDocumented,\n matchIsStale,\n matchMinCoverage,\n matchMaxCoverage,\n matchMinExportUsage,\n matchMaxExportUsage,\n matchMinComplexity,\n matchMaxComplexity,\n matchMinCognitiveComplexity,\n matchMaxCognitiveComplexity,\n matchMinCommits,\n matchMaxCommits,\n matchAny,\n];\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. Lives here (not in `filter.ts`) so `matchAny`\n * can recurse into `NodeQuery.any` sub-queries without a circular import between the two modules.\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","/** Filters a graph by applying NodeQuery predicates: category, type, tag, path, imports, coverage, and more. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport { matchNode } from \"./matchers\";\nimport type { NodeQuery } from \"./types\";\n\nexport { matchNode } from \"./matchers\";\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 const direction = query.sortDir === \"asc\" ? -1 : 1;\n resultNodes.sort((nodeA, nodeB) => {\n let diff = 0;\n if (query.sort === \"size\") diff = nodeB.size - nodeA.size;\n else if (query.sort === \"imports\") diff = nodeB.imports.length - nodeA.imports.length;\n else if (query.sort === \"commitCount90d\")\n diff = (nodeB.commitCount90d ?? 0) - (nodeA.commitCount90d ?? 0);\n else if (query.sort === \"exportUsage\")\n diff = (nodeB.avgExportUsage ?? 0) - (nodeA.avgExportUsage ?? 0);\n else if (query.sort === \"complexity\")\n diff = (nodeB.complexity ?? 0) - (nodeA.complexity ?? 0);\n else if (query.sort === \"cognitiveComplexity\")\n diff = (nodeB.cognitiveComplexity ?? 0) - (nodeA.cognitiveComplexity ?? 0);\n return diff * direction;\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 and applies a single `key:value` clause onto `query`, mutating it in\n * place. Shared by the top-level comma-split loop and by each `|`-separated clause inside an\n * `any(...)` OR-group, so both contexts recognize exactly the same set of keys.\n * @param {NodeQuery} query - The query object to mutate with this clause's parsed value.\n * @param {string} part - A single `key:value` clause (no surrounding commas).\n * @returns {void}\n */\nfunction applyClause(query: NodeQuery, part: string): void {\n const colonIdx = part.indexOf(\":\");\n if (colonIdx === -1) return;\n const key = part.slice(0, colonIdx).trim().toLowerCase();\n const value = part.slice(colonIdx + 1).trim();\n if (!key || !value) return;\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\n | \"size\"\n | \"imports\"\n | \"commitCount90d\"\n | \"exportUsage\"\n | \"complexity\"\n | \"cognitiveComplexity\";\n break;\n case \"sortdir\":\n query.sortDir = value.toLowerCase() === \"asc\" ? \"asc\" : \"desc\";\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 case \"mincomplexity\":\n query.minComplexity = parseInt(value, 10);\n break;\n case \"maxcomplexity\":\n query.maxComplexity = parseInt(value, 10);\n break;\n case \"mincognitivecomplexity\":\n query.minCognitiveComplexity = parseInt(value, 10);\n break;\n case \"maxcognitivecomplexity\":\n query.maxCognitiveComplexity = parseInt(value, 10);\n break;\n case \"mincommits\":\n query.minCommits = parseInt(value, 10);\n break;\n case \"maxcommits\":\n query.maxCommits = parseInt(value, 10);\n break;\n case \"isdocumented\":\n query.isDocumented = value.toLowerCase() !== \"false\";\n break;\n case \"isstale\":\n query.isStale = value.toLowerCase() !== \"false\";\n break;\n case \"lastauthor\":\n query.lastAuthor = value;\n break;\n }\n}\n\n/**\n * @description Parses an `any(clause|clause|...)` OR-group token into an array of single-clause\n * sub-queries, each parsed independently via `applyClause`. Each `|`-separated clause is a\n * single `key:value` pair — multi-key sub-AND clauses inside a group are not supported.\n * @param {string} inner - The group contents, with the `any(`/`)` wrapper already stripped.\n * @returns {NodeQuery[]} One `NodeQuery` per non-empty `|`-separated clause.\n */\nfunction parseAnyGroup(inner: string): NodeQuery[] {\n const subQueries: NodeQuery[] = [];\n for (const clause of inner.split(\"|\")) {\n const trimmedClause = clause.trim();\n if (!trimmedClause) continue;\n const subQuery: NodeQuery = {};\n applyClause(subQuery, trimmedClause);\n if (Object.keys(subQuery).length > 0) subQueries.push(subQuery);\n }\n return subQueries;\n}\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 * A token of the form `any(key:val|key:val)` is parsed as an OR-group of single-key clauses\n * and accumulates into `query.any`, ANDed with every other top-level key in the string.\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 trimmedPart = part.trim();\n if (trimmedPart.toLowerCase().startsWith(\"any(\") && trimmedPart.endsWith(\")\")) {\n const inner = trimmedPart.slice(4, -1);\n const subQueries = parseAnyGroup(inner);\n if (subQueries.length > 0) query.any = [...(query.any ?? []), ...subQueries];\n continue;\n }\n applyClause(query, part);\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 { applyAtPrefixedTagProp, TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\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 return applyAtPrefixedTagProp(absPath, source, tags, \"tags\");\n }\n}\n","/** Shared TypeScript AST helpers for framework-specific tag injection strategies. */\nimport path from \"node:path\";\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/**\n * @description Bounds-checked array access. Throws instead of silently returning `undefined`\n * when `index` is out of range — used in place of `arr[index]!` so a violated invariant fails\n * loudly instead of propagating `undefined` into position math.\n * @param {readonly T[]} arr - Array to index into.\n * @param {number} index - Index expected to be in range.\n * @returns {T} The element at `index`.\n */\nfunction at<T>(arr: readonly T[], index: number): T {\n const value = arr[index];\n if (value === undefined) {\n throw new Error(`Index ${index} out of bounds (length ${arr.length})`);\n }\n return value;\n}\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 = at(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 = at(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 = at(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: at(args, i - 1).getEnd(), end: arg.getEnd(), text: \"\" };\n }\n\n const prop = at(arg.properties, idx);\n if (idx === arg.properties.length - 1) {\n // Last property — also remove the preceding comma\n return { start: at(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: at(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/**\n * @description Strips a leading `@` from each tag so previously injected, `@`-prefixed tags\n * can be compared against freshly computed (unprefixed) tag names.\n * @param {string[]} raw - Tag strings as read from the source, possibly `@`-prefixed.\n * @returns {string[]} The same tags with any leading `@` removed.\n */\nexport function stripAtPrefix(raw: string[]): string[] {\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\n/**\n * @description Shared `apply()` body for frameworks that store tags as a single array-valued\n * options property (e.g. Cypress's `tags`, Playwright's `tag`), each entry conventionally\n * prefixed with `@`. Parses the file, finds top-level describe/test/it calls, checks\n * idempotency against the existing (unprefixed) tag set, then injects or removes the property.\n * @param {string} absPath - Absolute file path (used for parser diagnostics only, not I/O).\n * @param {string} source - Current file contents.\n * @param {string[]} tags - Sorted, unique tag names to write. An empty array removes the property.\n * @param {string} propName - Name of the options property this framework uses (e.g. `\"tags\"` or `\"tag\"`).\n * @returns {string} Potentially modified source (equal to `source` when no change is needed).\n */\nexport function applyAtPrefixedTagProp(\n absPath: string,\n source: string,\n tags: string[],\n propName: string,\n): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n const [firstCall] = calls;\n if (!firstCall) return source;\n\n const rawExisting = readArrayProp(firstCall, propName, sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(stripAtPrefix(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const literal = toArrayLiteral(sortedTags.map((tag) => `@${tag}`));\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, propName, sf)\n : buildInjectReplacement(call, propName, literal, sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\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 `${[\"# <mokosh-tags>\", ...tags.map((tag) => `@${tag}`), \"# </mokosh-tags>\"].join(\"\\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 { applyAtPrefixedTagProp, TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\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 return applyAtPrefixedTagProp(absPath, source, tags, \"tag\");\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 lines.forEach((line, i) => {\n if (line.trimStart().startsWith(\"import \") || line.trimStart().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 const offset = lines.slice(0, lastImportLine + 1).reduce((sum, line) => sum + line.length + 1, 0); // +1 for \\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 const [firstCall] = calls;\n if (!firstCall) return stripped;\n\n // Idempotency check — if first call already has the exact sorted tags, nothing to do\n const existing = readArrayProp(firstCall, \"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 Piscina from \"piscina\";\nimport { type GitFileStats, getRepoGitStats } 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 enrichDocDrift,\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/** Directory names skipped by `walkProject`'s discovery passes (test files, docs), independent of `DEFAULT_IGNORE_DIRS` scanning. */\nconst WALK_IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n]);\n\n/** Below this many files, the worker-pool spin-up cost outweighs the parallelism benefit — parse in-process instead. */\nconst DEFAULT_MIN_FILES_FOR_POOL = 20;\n\n/** Configures whether/how `parseFile` calls are offloaded to a `piscina` worker pool. `false` always parses in-process. */\nexport type ParallelParsingOption = boolean | { minFiles?: number; maxThreads?: number };\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 walking the file system from a set of entry points.\n *\n * Responsibilities:\n * - Parsing each reachable source file via {@link parseFile} (optionally offloaded to a worker pool)\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 * **Traversal shape:** a file's imports are only known once it's been parsed, so discovery is\n * inherently incremental. Rather than a strict recursive depth-first walk, `build` runs a\n * queue-pumped wavefront: each round parses/resolves every currently-queued file in parallel\n * (`Promise.all`), and files discovered during that round join the next round's queue. This\n * lets `getNode` dispatch parses to a worker pool concurrently instead of one at a time. Nothing\n * downstream depends on discovery order, only on the `visited` set being consulted synchronously\n * before a file is queued (see {@link GraphBuilder.discover}), which keeps dedup race-free even\n * though many files are mid-parse at once.\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 queue: 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 private pool: Piscina | null = null;\n private gitStatsMap: Map<string, GitFileStats> | null = null;\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 * @param parallelParsing - Controls worker-pool offloading of `parseFile`. `true`/omitted enables it once a cheap pre-scan finds at least `minFiles` (default 20) files under `rootDir`; `false` always parses in-process; an object overrides `minFiles`/`maxThreads`.\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 private readonly parallelParsing: ParallelParsingOption = true,\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 seeds the queue; imports are resolved and unvisited local files join the\n * next wavefront round, continuing until the full reachable subgraph is covered. Test-node\n * tags are applied as a final post-processing step because they depend on the fully connected\n * graph (e.g. a file is \"test\" if something imports it with a `.test.` path, which can only be\n * 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\n await this.initPool();\n if (this.enableGitStats) {\n this.gitStatsMap = getRepoGitStats(this.rootDir);\n }\n try {\n for (const entryPath of entryPaths) this.enqueue(entryPath);\n await this.drain();\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 // Docs are never reachable from library entry points either (code doesn't import markdown),\n // and they commonly live outside the entry points' subtree entirely (top-level README, docs/),\n // so this scans the full rootDir rather than reusing the test-files' common-ancestor scope.\n await this.processDocFiles();\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 enrichDocDrift(this.graph.nodes);\n if (this.coverageMap.size > 0) enrichCoverage(this.graph.nodes, this.coverageMap);\n return new Graph(this.graph.nodes);\n } finally {\n if (this.pool) {\n await this.pool.destroy();\n this.pool = null;\n }\n }\n }\n\n /**\n * @description Decides whether to spin up a worker pool for this build and constructs it if\n * so. Skipped entirely when `parallelParsing` is `false`, or when a cheap pre-scan of\n * `rootDir` (capped at `minFiles`, so it never walks more than necessary) finds fewer than\n * `minFiles` files — small builds (the common unit-test case) never pay pool spin-up cost.\n * If pool construction itself throws (e.g. a sandboxed environment without `worker_threads`\n * permission), falls back to in-process parsing for the whole build instead of failing it.\n */\n private async initPool(): Promise<void> {\n if (this.parallelParsing === false) return;\n\n const opts = typeof this.parallelParsing === \"object\" ? this.parallelParsing : {};\n const minFiles = opts.minFiles ?? DEFAULT_MIN_FILES_FOR_POOL;\n if (minFiles > 0 && !this.hasAtLeastFiles(this.rootDir, minFiles)) return;\n\n try {\n const workerFilename = path.join(__dirname, \"parse-worker.js\");\n this.pool = new Piscina({\n filename: workerFilename,\n ...(opts.maxThreads !== undefined ? { maxThreads: opts.maxThreads } : {}),\n });\n } catch (err) {\n process.stderr.write(\n `\\nWarning: failed to start parse worker pool, falling back to synchronous parsing: ${err}\\n`,\n );\n this.pool = null;\n }\n }\n\n /**\n * @description Cheaply checks whether `dir` contains at least `threshold` files, stopping the\n * walk as soon as the threshold is met rather than counting exhaustively.\n * @param dir - Directory to scan (recursively, skipping `WALK_IGNORE_DIRS`).\n * @param threshold - Minimum file count to confirm.\n * @returns Whether at least `threshold` files were found.\n */\n private hasAtLeastFiles(dir: string, threshold: number): boolean {\n let count = 0;\n const stack = [dir];\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) continue;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(current, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!WALK_IGNORE_DIRS.has(entry.name)) stack.push(path.join(current, entry.name));\n } else if (entry.isFile()) {\n count++;\n if (count >= threshold) return true;\n }\n }\n }\n return count >= threshold;\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 matchesTest = (entry: fs.Dirent) =>\n patterns.some((pattern) => entry.name.includes(pattern));\n\n this.walkProject(scanRoot, matchesTest);\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()) this.walkProject(candidate, matchesTest);\n } catch {\n // not present\n }\n }\n dir = parent;\n }\n\n await this.drain();\n }\n\n /**\n * @description Scans the full project root for `.md`/`.mdx` files and processes them into the\n * graph. Docs are never reachable from library entry points (code doesn't import markdown) and,\n * unlike test files, aren't confined to the entry points' subtree — a top-level `README.md` or\n * `docs/` directory is common — so this walks `rootDir` directly rather than reusing\n * `processTestFiles`'s narrower common-ancestor scope.\n */\n private async processDocFiles(): Promise<void> {\n this.walkProject(\n this.rootDir,\n (entry) => entry.name.endsWith(\".md\") || entry.name.endsWith(\".mdx\"),\n );\n await this.drain();\n }\n\n /**\n * @description Recursively walks a directory, enqueueing every file that satisfies `matches`\n * for processing. Shared by `processTestFiles` and `processDocFiles`, whose discovery passes\n * only differ in which files they're looking for and where they start. Purely synchronous\n * directory traversal — the actual parse/resolve work happens later, in `drain`.\n * @param scanRoot - Directory to walk.\n * @param matches - Predicate tested against each file entry; matching files are enqueued.\n */\n private walkProject(scanRoot: string, matches: (entry: fs.Dirent) => boolean): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(scanRoot, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(scanRoot, entry.name);\n if (entry.isDirectory()) {\n if (!WALK_IGNORE_DIRS.has(entry.name)) this.walkProject(fullPath, matches);\n } else if (entry.isFile() && matches(entry)) {\n this.enqueue(fullPath);\n }\n }\n }\n\n /**\n * @description Marks `filePath` as visited and, if it wasn't already, queues it for\n * processing in the next `drain` round.\n *\n * The `visited` guard prevents re-processing files encountered via multiple import paths\n * (diamond dependencies). It is checked-and-set synchronously, with no `await` in between,\n * so concurrent callers mid-parse in the same `drain` round cannot race each other onto the\n * same file.\n * @param filePath - Absolute path of the file to enqueue.\n */\n private enqueue(filePath: string): void {\n if (this.visited.has(filePath)) return;\n this.visited.add(filePath);\n this.showProgress();\n this.queue.push(filePath);\n }\n\n /**\n * @description Drains the queue in wavefront rounds: every file currently queued is parsed\n * and resolved in parallel via `Promise.all`, and any new files discovered during that round\n * (via `resolveImports` → `enqueue`) form the next round's queue. Repeats until empty.\n */\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue;\n this.queue = [];\n await Promise.all(batch.map((filePath) => this.finishFile(filePath)));\n }\n }\n\n /**\n * @description Parses a single queued file and registers it in the graph, then enqueues its\n * local imports for the next wavefront round.\n * @param filePath - Absolute path of the file to process.\n */\n private async finishFile(filePath: string): Promise<void> {\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 enrichLibraryTags(node.imports, node.tags);\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 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 this.parseContent(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 Parses file content, dispatching to the worker pool when one is running for\n * this build, or parsing in-process otherwise. A worker's thrown error surfaces as a normal\n * rejected promise, so callers need no special handling for the pooled vs. in-process case.\n * @param filePath - Absolute path of the file, used to select the language parser.\n * @param content - Raw source content of the file.\n */\n private parseContent(filePath: string, content: string): ReturnType<typeof parseFile> {\n if (this.pool) {\n return this.pool.run({ filePath, content });\n }\n return parseFile(filePath, content);\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, by looking\n * up the repo-wide stats map computed once in {@link build} (see `getRepoGitStats`) rather than\n * spawning a `git log` per file. Silently no-ops for files not tracked by git.\n * @param node - The node to mutate in place.\n * @param relativePath - Project-relative path used as the lookup key.\n */\n private attachGitStats(node: FileNode, relativePath: string): void {\n const git = this.gitStatsMap?.get(relativePath);\n if (!git) return;\n node.commitCount90d = git.commitCount90d;\n if (git.lastAuthor !== undefined) node.lastAuthor = git.lastAuthor;\n if (git.lastCommitAt !== undefined) node.lastCommitAt = git.lastCommitAt;\n }\n\n /**\n * @description Resolves each import's raw specifier to a concrete path and, for local imports,\n * enqueues the target file for the next wavefront round.\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 this.enqueue(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 repo-wide commit activity stats via getRepoGitStats. */\nimport { execFileSync } 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 [\"diff\", \"--name-only\"],\n [\"diff\", \"--cached\", \"--name-only\"],\n [\"ls-files\", \"--others\", \"--exclude-standard\"],\n ];\n\n const allFiles = commands.flatMap((args) => {\n try {\n const output = execFileSync(\"git\", args, {\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\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 /** Unix timestamp (ms) of the most recent commit touching this file, regardless of the 90-day window. Used for doc-drift staleness comparisons. */\n lastCommitAt: number | undefined;\n}\n\n/** Null-byte-prefixed commit header emitted before each commit's file list, so header lines are unambiguous even when git's blank-line placement around `--name-status` output varies. */\nconst COMMIT_HEADER_FORMAT = \"%x00%H%x09%ae%x09%at\";\n\n/** `--name-status` output can be sizeable for repos with long history; the default 1MB execFileSync buffer is too small. */\nconst MAX_LOG_BUFFER_BYTES = 256 * 1024 * 1024;\n\n/**\n * @description Walks `git log --name-status` output (produced with {@link COMMIT_HEADER_FORMAT}) and\n * invokes `onFile` for every (file, author, timestamp) touch point, newest commit first. Rename\n * entries (`R<score>\\t<old>\\t<new>`) are attributed to the new path, approximating `--follow`\n * semantics in aggregate across the whole repo rather than per file.\n * @param output - Raw stdout from a `git log --name-status --format=<COMMIT_HEADER_FORMAT>` invocation.\n * @param onFile - Called once per file touched by each commit, in newest-to-oldest order.\n */\nfunction walkNameStatusLog(\n output: string,\n onFile: (filePath: string, authorEmail: string, timestampMs: number) => void,\n): void {\n let authorEmail = \"\";\n let timestampMs = 0;\n\n for (const line of output.split(\"\\n\")) {\n if (line.startsWith(\"\\u0000\")) {\n const [, email, at] = line.slice(1).split(\"\\t\");\n authorEmail = email ?? \"\";\n timestampMs = at ? Number(at) * 1000 : 0;\n continue;\n }\n if (line === \"\") continue;\n\n const parts = line.split(\"\\t\");\n const status = parts[0];\n if (!status) continue;\n const filePath = status.startsWith(\"R\") || status.startsWith(\"C\") ? parts[2] : parts[1];\n if (!filePath) continue;\n\n onFile(filePath, authorEmail, timestampMs);\n }\n}\n\n/**\n * @description Computes commit activity stats for every file in the repository with two `git log`\n * invocations total (independent of file count), instead of spawning per-file `git log` calls.\n * The first, bounded call covers `commitCount90d`/`lastAuthor`/`lastCommitAt` for files touched in\n * the last 90 days. The second, unbounded call only backfills `lastCommitAt` for files with no\n * recent history, since that field is used unconditionally for doc-staleness comparisons.\n * @param rootDir - Absolute path to the repository root, passed to `git -C` so the command works from any cwd.\n * @returns Map keyed by repo-relative path, as reported by git (matching `FileNode.path`). Empty if\n * `rootDir` isn't a git repository or git is unavailable.\n */\nexport function getRepoGitStats(rootDir: string): Map<string, GitFileStats> {\n const stats = new Map<string, GitFileStats>();\n\n try {\n const recentOutput = execFileSync(\n \"git\",\n [\n \"-C\",\n rootDir,\n \"log\",\n \"--since=90 days ago\",\n \"--name-status\",\n \"-M\",\n `--format=${COMMIT_HEADER_FORMAT}`,\n ],\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: MAX_LOG_BUFFER_BYTES },\n );\n\n walkNameStatusLog(recentOutput, (filePath, authorEmail, timestampMs) => {\n const existing = stats.get(filePath);\n if (existing) {\n existing.commitCount90d += 1;\n return;\n }\n stats.set(filePath, {\n commitCount90d: 1,\n lastAuthor: authorEmail || undefined,\n lastCommitAt: timestampMs || undefined,\n });\n });\n } catch {\n return stats;\n }\n\n try {\n const fullOutput = execFileSync(\n \"git\",\n [\"-C\", rootDir, \"log\", \"--name-status\", \"-M\", `--format=${COMMIT_HEADER_FORMAT}`],\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: MAX_LOG_BUFFER_BYTES },\n );\n\n walkNameStatusLog(fullOutput, (filePath, _authorEmail, timestampMs) => {\n if (stats.has(filePath)) return;\n stats.set(filePath, {\n commitCount90d: 0,\n lastAuthor: undefined,\n lastCommitAt: timestampMs || undefined,\n });\n });\n } catch {\n // Full-history fallback unavailable — files with no commits in the last 90 days simply lack lastCommitAt.\n }\n\n return stats;\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 case \".md\":\n case \".mdx\":\n return \"markdown\";\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, module exports, and tag annotations. */\nimport coffee from \"coffeescript\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\n\ninterface StringLiteralNode {\n value: string;\n originalValue?: string;\n}\n\ninterface CoffeeNode {\n constructor: { name: string };\n source?: StringLiteralNode;\n variable?: CoffeeNode & { base?: CoffeeNode; properties?: CoffeeNode[] };\n args?: Array<{ base?: StringLiteralNode }>;\n value?: unknown;\n base?: CoffeeNode;\n properties?: CoffeeNode[];\n name?: CoffeeNode;\n clause?: CoffeeNode;\n specifiers?: Array<{ identifier?: string; original?: { value?: string } }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Resolves the unquoted text of a CoffeeScript string-literal node. Prefers\n * `originalValue` (the compiler's own unescaped/unquoted form) and falls back to stripping\n * the surrounding quote characters from `.value` when `originalValue` isn't present.\n * @param node - The string-literal AST node, or `undefined`.\n * @returns The unquoted string content, or `undefined` if `node` has no string value.\n */\nfunction stringLiteralValue(node: StringLiteralNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.originalValue === \"string\") return node.originalValue;\n if (typeof node.value === \"string\") return stripQuotes(node.value);\n return undefined;\n}\n\n/**\n * @description Resolves the plain identifier name held by a CoffeeScript AST node, handling both\n * bare `IdentifierLiteral` nodes (`.value`) and `Value`-wrapped identifiers (`.base.value`) used\n * as the LHS of assignments and export clauses.\n * @param node - The AST node to resolve, or `undefined`.\n * @returns The identifier's string name, or `undefined` if `node` isn't a simple identifier.\n */\nfunction identifierName(node: CoffeeNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.value === \"string\") return node.value;\n if (typeof node.base?.value === \"string\") return node.base.value;\n return undefined;\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 = stringLiteralValue(node.source);\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 = stringLiteralValue(node.args?.[0]?.base);\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 Builds export symbols from a CommonJS `module.exports = { ... }` / `exports.foo = ...`\n * assignment's right-hand side: named exports for each field of an object literal, the class\n * name for `module.exports = class Foo`, or the referenced identifier's name for a bare\n * re-export (`module.exports = Widget`).\n * @param assignNode - The `Assign` AST node whose LHS already matched `module.exports` or `exports.<name>`.\n * @param propertyName - The member name accessed after `exports` (`baz` in `exports.baz = 3`), if any.\n * @returns Export symbols discovered on the assignment's right-hand side.\n */\nfunction exportsFromAssignment(\n assignNode: CoffeeNode,\n propertyName: string | undefined,\n): ExportedSymbol[] {\n if (propertyName) return [{ name: propertyName }];\n\n const rhs = assignNode.value as CoffeeNode | undefined;\n if (rhs?.constructor?.name === \"Class\") {\n const className = identifierName(rhs.variable);\n return className ? [{ name: className }] : [];\n }\n if (rhs?.base?.constructor?.name === \"Obj\" && Array.isArray(rhs.base.properties)) {\n // `foo: value` properties are Assign nodes (name via `.variable`); shorthand `{ foo }`\n // properties compile to a bare Value/IdentifierLiteral node instead.\n return rhs.base.properties\n .map((property) => identifierName(property.variable) ?? identifierName(property))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const bareName = identifierName(rhs);\n return bareName ? [{ name: bareName }] : [];\n}\n\n/**\n * @description Builds export symbols from an ES `export ...` declaration clause: `export { a, b }`\n * specifier lists, `export foo = ...` assignments, and any other clause shape carrying a\n * `.variable` (e.g. `export class Foo`).\n * @param clause - The `clause` payload of an `ExportNamedDeclaration` node.\n * @returns Export symbols discovered in the clause.\n */\nfunction exportsFromNamedClause(clause: CoffeeNode): ExportedSymbol[] {\n if (Array.isArray(clause.specifiers)) {\n return clause.specifiers\n .map((specifier) => specifier.identifier ?? specifier.original?.value)\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const name = identifierName(clause.variable) ?? identifierName(clause.variable?.base);\n return name ? [{ name }] : [];\n}\n\n/**\n * @description Inspects a single CoffeeScript AST node and appends any discovered import edge or\n * export symbol to the corresponding accumulator. Handles `ImportDeclaration`/`Call` (require)\n * for imports, and `module.exports`/`exports.<name>` assignments plus ES `export` declarations\n * for exports.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to inspect.\n * @param imports - Accumulator array that receives any discovered import edge.\n * @param exports - Accumulator array that receives any discovered export symbols.\n */\nfunction visitNode(\n filePath: string,\n node: CoffeeNode,\n imports: ImportEdge[],\n exports: ExportedSymbol[],\n): void {\n const className = node.constructor?.name;\n if (className === \"ImportDeclaration\") {\n const edge = edgeFromImportDeclaration(filePath, node);\n if (edge) imports.push(edge);\n } else if (className === \"Call\") {\n const edge = edgeFromRequireCall(filePath, node);\n if (edge) imports.push(edge);\n } else if (className === \"Assign\") {\n const base = identifierName(node.variable?.base);\n const propertyName = node.variable?.properties?.[0]?.name?.value;\n if (base === \"module\" && propertyName === \"exports\") {\n exports.push(...exportsFromAssignment(node, undefined));\n } else if (base === \"exports\" && typeof propertyName === \"string\") {\n exports.push(...exportsFromAssignment(node, propertyName));\n }\n } else if (className === \"ExportNamedDeclaration\" && node.clause) {\n exports.push(...exportsFromNamedClause(node.clause));\n } else if (className === \"ExportDefaultDeclaration\") {\n exports.push({ name: \"default\" });\n }\n}\n\n/**\n * @description Recursively walks the CoffeeScript AST and collects all import edges and export\n * symbols into the given accumulators. Skips `locationData` keys to prevent infinite cycles on\n * circular metadata references.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to walk.\n * @param imports - Accumulator array that receives all discovered import edges.\n * @param exports - Accumulator array that receives all discovered export symbols.\n */\nfunction traverse(\n filePath: string,\n node: CoffeeNode,\n imports: ImportEdge[],\n exports: ExportedSymbol[],\n): void {\n if (!node || typeof node !== \"object\") return;\n visitNode(filePath, node, imports, exports);\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, imports, exports);\n } else {\n traverse(filePath, child as CoffeeNode, imports, exports);\n }\n }\n}\n\n/**\n * @description Parses a CoffeeScript source file and extracts its import edges, module exports,\n * comment-marker tags, and file category. Uses the CoffeeScript compiler's `nodes()` API for\n * full AST traversal, capturing ES `import`/`export` declarations as well as CommonJS\n * `require()`/`module.exports` conventions. Falls back to empty imports/exports if the file\n * 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, exports, extracted tags, and resolved category.\n */\nexport function parseCoffeeScript(filePath: string, content: string): ParseResult {\n const tags = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tags);\n const imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n\n try {\n traverse(filePath, coffee.nodes(content) as unknown as CoffeeNode, imports, exports);\n } catch (_e) {\n // coffeescript compiler throws on invalid syntax; return what we have\n }\n\n const seenNames = new Set<string>();\n const dedupedExports = exports.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n\n return {\n imports,\n exports: dedupedExports,\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\n/**\n * @description Scans raw source text for `@tag <name>` comment annotations and collects the\n * tag names. Shared across languages whose parser has no dedicated comment/annotation AST\n * node (CoffeeScript, LiveScript, Lua), so tags are extracted via regex ahead of/instead of\n * full parsing. 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 */\nexport function extractTagAnnotations(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 an explicit `@tag test` annotation. Shared\n * classification rule for the regex-tagged languages (CoffeeScript, LiveScript, Lua).\n * @param filePath - Path to the file being classified.\n * @param tags - Tag names extracted from the file's content (see `extractTagAnnotations`).\n * @returns `\"test\"` if the file is a test file, `\"logic\"` otherwise.\n */\nexport function classifyTestOrLogic(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","/** 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. */\n\nimport path from \"node:path\";\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport { parser } from \"@lezer/go\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { collectFunctionComplexity, computeComplexity, receiverTypeName } from \"../complexity/go\";\nimport type { ParseResult, RawCallEdge } 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 const packageIdents = new Map<string, 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 // Track the identifier code uses to reference this package: an explicit alias\n // (DefName), or — absent one — the conventional last path segment.\n const aliasNode = cursor.node.getChild(\"DefName\");\n const ident = aliasNode\n ? content.slice(aliasNode.from, aliasNode.to)\n : specifier.split(\"/\").pop();\n if (ident && ident !== \"_\" && ident !== \".\") packageIdents.set(ident, specifier);\n }\n break;\n }\n\n case \"FunctionDecl\":\n case \"MethodDecl\":\n case \"TypeDecl\":\n case \"VarDecl\":\n case \"ConstDecl\": {\n // For FunctionDecl the DefName is a direct child.\n // For MethodDecl the receiver name is a direct child too, so match FieldName instead.\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(\"FieldName\") ??\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 const { complexity, cognitiveComplexity } = computeComplexity(tree.topNode, content);\n const functions = collectFunctionComplexity(tree, content);\n const rawCallEdges = category === \"test\" ? [] : collectRawCallEdges(tree, content, packageIdents);\n\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 rawCallEdges,\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\n };\n}\n\n/**\n * @description Walks every top-level exported `FunctionDecl` and every `MethodDecl` (regardless\n * of export — a receiver always names the enclosing type, mirroring how the TS parser tracks\n * class methods regardless of their own visibility), recording a `RawCallEdge` for each call to\n * a package-qualified function (`pkg.Func()`) where `pkg` resolves to a tracked import.\n * Unqualified calls (to same-file functions) are not cross-file dependencies and are skipped.\n * @param {Tree} tree - The parsed @lezer/go tree.\n * @param {string} content - Full source text.\n * @param {Map<string, string>} packageIdents - Maps the identifier code uses for a package\n * (alias or default last-path-segment) to its raw import specifier.\n * @returns {RawCallEdge[]} One edge per package-qualified call to a known imported package.\n */\nfunction collectRawCallEdges(\n tree: Tree,\n content: string,\n packageIdents: Map<string, string>,\n): RawCallEdge[] {\n const edges: RawCallEdge[] = [];\n\n function walkBody(node: SyntaxNode, callerName: string): void {\n if (node.type.name === \"CallExpr\") {\n const callee = node.firstChild;\n if (callee?.type.name === \"SelectorExpr\") {\n const pkgIdentNode = callee.firstChild;\n const fieldNode = callee.getChild(\"FieldName\");\n if (pkgIdentNode?.type.name === \"VariableName\" && fieldNode) {\n const pkgIdent = content.slice(pkgIdentNode.from, pkgIdentNode.to);\n const toSpecifier = packageIdents.get(pkgIdent);\n if (toSpecifier) {\n const to = content.slice(fieldNode.from, fieldNode.to);\n edges.push({ from: callerName, to, toSpecifier });\n }\n }\n }\n }\n let child = node.firstChild;\n while (child) {\n walkBody(child, callerName);\n child = child.nextSibling;\n }\n }\n\n const cursor = tree.cursor();\n do {\n if (cursor.name === \"FunctionDecl\") {\n const nameNode = cursor.node.getChild(\"DefName\");\n const body = cursor.node.getChild(\"Block\");\n if (nameNode && body) {\n const name = content.slice(nameNode.from, nameNode.to);\n if (/^[A-Z]/.test(name)) walkBody(body, name);\n }\n } else if (cursor.name === \"MethodDecl\") {\n const fieldNode = cursor.node.getChild(\"FieldName\");\n const body = cursor.node.getChild(\"Block\");\n if (fieldNode && body) {\n const methodName = content.slice(fieldNode.from, fieldNode.to);\n const receiver = receiverTypeName(cursor.node, content);\n walkBody(body, receiver ? `${receiver}.${methodName}` : methodName);\n }\n }\n } while (cursor.next());\n\n return edges;\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","/** Shared low-level helpers for walking @lezer/common SyntaxNode trees during complexity\n * analysis. The actual cyclomatic/cognitive scoring logic in ./go.ts and ./python.ts is *not*\n * shared beyond this: Go's if/else-if is nested (like TS), while Python's if/elif/else and\n * try/except are flat sibling sequences within one node — different enough that a forced common\n * abstraction would need as much branching as it saves. See docs/adr-011-go-python-call-edges.md. */\nimport type { SyntaxNode } from \"@lezer/common\";\n\n/**\n * @description Collects the direct children of a Lezer syntax node into an array, in document\n * order, by walking the `firstChild`/`nextSibling` chain. Lezer nodes have no `getChildren()`\n * equivalent for \"all children\", unlike the TS compiler API's `forEachChild`.\n * @param {SyntaxNode} node - The node whose direct children to collect.\n * @returns {SyntaxNode[]} The node's direct children, in order.\n */\nexport function childrenOf(node: SyntaxNode): SyntaxNode[] {\n const result: SyntaxNode[] = [];\n let child = node.firstChild;\n while (child) {\n result.push(child);\n child = child.nextSibling;\n }\n return result;\n}\n\n/**\n * @description Resolves the 1-indexed source line a byte offset falls on by counting newlines\n * before it. @lezer trees carry no line/column info by default, unlike the TS compiler API's\n * `SourceFile.getLineAndCharacterOfPosition`.\n * @param {string} content - Full source text.\n * @param {number} pos - Byte offset into `content`.\n * @returns {number} The 1-indexed line number containing `pos`.\n */\nexport function lineAt(content: string, pos: number): number {\n let line = 1;\n for (let i = 0; i < pos && i < content.length; i++) {\n if (content[i] === \"\\n\") line++;\n }\n return line;\n}\n","/** Computes McCabe cyclomatic complexity and cognitive complexity for Go source, mirroring the\n * TypeScript algorithm in ../complexity.ts but walking the @lezer/go SyntaxNode tree instead. */\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport type { FunctionComplexity } from \"../../types/node\";\nimport { childrenOf, lineAt } from \"./lezer-utils\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for a Go AST node: every independent\n * decision point counts (base 1) — `if`, `for`, non-default `switch`/`select` cases, and each\n * `&&` / `||` operator. Go has no `try`/`catch` or ternary operator, so those TS decision\n * points have no Go equivalent.\n * @param {SyntaxNode} rootNode - The AST root node to analyse — the whole file's top node for\n * file-level totals, or a `FunctionDecl`/`MethodDecl`/`FunctionLiteral` node to score it alone.\n * @param {string} content - Full source text, used to read operator token text.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: SyntaxNode, content: string): number {\n let complexity = 1;\n\n function walk(node: SyntaxNode): void {\n switch (node.type.name) {\n case \"IfStatement\":\n case \"ForStatement\":\n complexity++;\n break;\n case \"Case\": {\n // Case covers both `case` and `default` clauses; only `case` is a decision point.\n if (node.firstChild?.type.name === \"case\") complexity++;\n break;\n }\n case \"LogicOp\": {\n const text = content.slice(node.from, node.to);\n if (text === \"&&\" || text === \"||\") complexity++;\n break;\n }\n }\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for a Go AST\n * node, tracking how hard the code is to read by adding a nesting penalty. Mirrors the\n * TypeScript algorithm: `if`/`for`/`switch` increment by `1 + current nesting depth` and\n * increase depth for their children; chained `else if` gets +1 flat; a bare `else` gets +1\n * flat; `&&`/`||` each add +1 flat; nested function literals add `1 + depth`.\n * @param {SyntaxNode} rootNode - The AST root node to analyse (nesting depth resets to 0 here).\n * @param {string} content - Full source text, used to read operator token text.\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: SyntaxNode, content: string): number {\n let cognitive = 0;\n\n function walk(node: SyntaxNode, depth: number, isElseIf: boolean): void {\n const name = node.type.name;\n\n if (name === \"IfStatement\") {\n cognitive += isElseIf ? 1 : 1 + depth;\n const kids = childrenOf(node);\n const bodyDepth = isElseIf ? depth : depth + 1;\n\n const cond = kids[1];\n if (cond && cond.type.name !== \"Block\") walk(cond, bodyDepth, false);\n\n const thenBlock = kids.find((k) => k.type.name === \"Block\");\n if (thenBlock) walk(thenBlock, bodyDepth, false);\n\n const elseIndex = kids.findIndex((k) => k.type.name === \"else\");\n if (elseIndex >= 0) {\n const after = kids[elseIndex + 1];\n if (after?.type.name === \"IfStatement\") {\n walk(after, depth, true);\n } else if (after) {\n cognitive += 1;\n walk(after, depth + 1, false);\n }\n }\n return;\n }\n\n if (name === \"ForStatement\" || name === \"SwitchStatement\" || name === \"SelectStatement\") {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1, false);\n child = child.nextSibling;\n }\n return;\n }\n\n if (name === \"LogicOp\") {\n const text = content.slice(node.from, node.to);\n if (text === \"&&\" || text === \"||\") cognitive += 1;\n }\n\n const isNestedFunctionLiteral = depth > 0 && name === \"FunctionLiteral\";\n if (isNestedFunctionLiteral) {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1, false);\n child = child.nextSibling;\n }\n return;\n }\n\n let child = node.firstChild;\n while (child) {\n walk(child, depth, false);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode, 0, false);\n return cognitive;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and cognitive complexity for a Go AST\n * node by composing `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {SyntaxNode} node - The AST root node to analyse.\n * @param {string} content - Full source text.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores.\n */\nexport function computeComplexity(\n node: SyntaxNode,\n content: string,\n): { complexity: number; cognitiveComplexity: number } {\n return {\n complexity: computeCyclomaticComplexity(node, content),\n cognitiveComplexity: computeCognitiveComplexity(node, content),\n };\n}\n\n/**\n * @description Reads the receiver type name off a `MethodDecl` node (e.g. `Receiver` from\n * `func (r *Receiver) Method()`), unwrapping a pointer receiver if present.\n * @param {SyntaxNode} methodDecl - The `MethodDecl` node.\n * @param {string} content - Full source text, used to slice the type name.\n * @returns {string | undefined} The receiver's bare type name, or `undefined` if not found.\n */\nexport function receiverTypeName(methodDecl: SyntaxNode, content: string): string | undefined {\n const receiverParams = methodDecl.getChild(\"Parameters\");\n const receiverParam = receiverParams?.getChild(\"Parameter\");\n const typeNode =\n receiverParam?.getChild(\"TypeName\") ??\n receiverParam?.getChild(\"PointerType\")?.getChild(\"TypeName\");\n return typeNode ? content.slice(typeNode.from, typeNode.to) : undefined;\n}\n\n/**\n * @description Walks the entire Go source tree and records complexity for every named\n * `FunctionDecl` and `MethodDecl` — top-level or nested. Methods are qualified as\n * `ReceiverType.MethodName` to mirror the TS parser's `ClassName.methodName` convention.\n * @param {Tree} tree - The parsed @lezer/go tree.\n * @param {string} content - Full source text.\n * @returns {FunctionComplexity[]} Per-function complexity entries, in traversal order.\n */\nexport function collectFunctionComplexity(tree: Tree, content: string): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n const cursor = tree.cursor();\n\n do {\n if (cursor.name === \"FunctionDecl\") {\n const nameNode = cursor.node.getChild(\"DefName\");\n if (nameNode) {\n const name = content.slice(nameNode.from, nameNode.to);\n const { complexity, cognitiveComplexity } = computeComplexity(cursor.node, content);\n results.push({ name, line: lineAt(content, cursor.from), complexity, cognitiveComplexity });\n }\n } else if (cursor.name === \"MethodDecl\") {\n const nameNode = cursor.node.getChild(\"FieldName\");\n if (nameNode) {\n const methodName = content.slice(nameNode.from, nameNode.to);\n const receiver = receiverTypeName(cursor.node, content);\n const name = receiver ? `${receiver}.${methodName}` : methodName;\n const { complexity, cognitiveComplexity } = computeComplexity(cursor.node, content);\n results.push({ name, line: lineAt(content, cursor.from), complexity, cognitiveComplexity });\n }\n }\n } while (cursor.next());\n\n return results;\n}\n","/** Parses LiveScript files to extract import edges, module exports, and tag annotations. */\n// @ts-expect-error\nimport ls from \"livescript\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\n\ninterface LiveScriptNode {\n constructor: { name: string };\n type?: string;\n value?: string;\n right?: LiveScriptNode;\n left?: LiveScriptNode;\n head?: LiveScriptNode & { value?: string; verb?: string };\n base?: LiveScriptNode;\n verb?: string;\n key?: LiveScriptNode & { name?: string };\n name?: string;\n title?: LiveScriptNode;\n items?: LiveScriptNode[];\n val?: LiveScriptNode;\n tails?: Array<{\n constructor: { name: string };\n type?: string;\n args?: Array<{ value: string }>;\n key?: LiveScriptNode & { name?: string };\n }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Resolves the plain identifier name held by a LiveScript AST node, handling bare\n * `Var` nodes (`.value`) and `Value`-wrapped identifiers (`.base.value`).\n * @param node - The AST node to resolve, or `undefined`.\n * @returns The identifier's string name, or `undefined` if `node` isn't a simple identifier.\n */\nfunction identifierName(node: LiveScriptNode | undefined): string | undefined {\n if (!node) return undefined;\n if (typeof node.value === \"string\") return node.value;\n if (typeof node.base?.value === \"string\") return node.base.value;\n return undefined;\n}\n\n/**\n * @description Builds export symbols from the right-hand side of a `module.exports = ...` /\n * `export <name> = ...` assignment: named exports for each field of an object literal, the\n * class name for `= class Foo`, or the referenced identifier's name for a bare re-export.\n * @param rhs - The assignment's right-hand-side AST node.\n * @returns Export symbols discovered on the right-hand side.\n */\nfunction exportsFromValue(rhs: LiveScriptNode | undefined): ExportedSymbol[] {\n if (!rhs) return [];\n if (rhs.constructor?.name === \"Class\") {\n const className = identifierName(rhs.title);\n return className ? [{ name: className }] : [];\n }\n if (rhs.constructor?.name === \"Obj\" && Array.isArray(rhs.items)) {\n return rhs.items\n .map((prop) => identifierName(prop.key) ?? identifierName(prop.val))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n const bareName = identifierName(rhs);\n return bareName ? [{ name: bareName }] : [];\n}\n\n/**\n * @description Inspects a single AST node and returns export symbols if the node represents a\n * `module.exports = ...` / `exports.<name> = ...` CommonJS assignment, or an `export <decl>` /\n * `export {a, b}` LiveScript declaration (both compile through the `out` verb), or an empty\n * array otherwise.\n * @param node - The AST node to inspect.\n * @returns Export symbols discovered on this node, if any.\n */\nfunction extractExports(node: LiveScriptNode): ExportedSymbol[] {\n const type = node.constructor?.name || node.type;\n\n if (type === \"Assign\" && node.left?.constructor?.name === \"Chain\") {\n const chain = node.left;\n const base = identifierName(chain.head);\n const tailIndex = chain.tails?.[0];\n const propertyName = tailIndex?.key?.name;\n\n if (base === \"module\" && propertyName === \"exports\") {\n return exportsFromValue(node.right);\n }\n if (base === \"exports\" && typeof propertyName === \"string\") {\n return [{ name: propertyName }];\n }\n if (chain.head?.verb === \"out\" && typeof propertyName === \"string\") {\n return [{ name: propertyName }];\n }\n }\n\n if (type === \"Import\" && node.left?.verb === \"out\") {\n const rhs = node.right;\n if (rhs?.constructor?.name === \"Obj\" && Array.isArray(rhs.items)) {\n return rhs.items\n .map((prop) => identifierName(prop.val) ?? identifierName(prop.key))\n .filter((name): name is string => !!name)\n .map((name) => ({ name }));\n }\n }\n\n return [];\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 and export\n * symbols found 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 * @param exports - Accumulator array that receives all discovered export symbols.\n * @returns All ImportEdge values found in this node and its descendants.\n */\nfunction collectEdges(\n node: LiveScriptNode,\n filePath: string,\n exports: ExportedSymbol[],\n): 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 exports.push(...extractExports(node));\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, exports));\n } else {\n edges.push(...collectEdges(child as LiveScriptNode, filePath, exports));\n }\n }\n\n return edges;\n}\n\n/**\n * @description Parses a LiveScript source file to extract its dependency edges, module exports,\n * comment-marker tags, and file category. Handles both ES-style `import` statements and\n * CommonJS `require()` calls, plus the `module.exports`/`exports.<name>` and `export ...`\n * conventions. 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, exports, extracted tags, and the file category.\n */\nexport function parseLiveScript(filePath: string, content: string): ParseResult {\n const tags = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tags);\n let imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n\n try {\n imports = collectEdges(ls.ast(content) as LiveScriptNode, filePath, exports);\n } catch (_e) {\n // ignore parse errors\n }\n\n const seenNames = new Set<string>();\n const dedupedExports = exports.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n\n return {\n imports,\n exports: dedupedExports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Lua source files via luaparse to extract require() dependency edges, module exports, and @tag annotations. */\nimport type { Chunk, Node, Statement } from \"luaparse\";\nimport luaparse from \"luaparse\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { classifyTestOrLogic, extractTagAnnotations, stripQuotes } from \"../utils\";\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 Collects the names of top-level local variables initialized as an empty (or any)\n * table constructor — the conventional `local M = {}` module-table idiom — so that later\n * assignments to `M.<field>` can be recognized as module exports.\n * @param topLevelStatements - Statements at the root of the chunk body.\n * @returns Set of identifier names bound to a table constructor at the top level.\n */\nfunction collectModuleTableNames(topLevelStatements: Statement[]): Set<string> {\n const moduleTableNames = new Set<string>();\n for (const statement of topLevelStatements) {\n if (statement.type !== \"LocalStatement\") continue;\n statement.variables.forEach((variable, index) => {\n const initializer = statement.init[index];\n if (initializer?.type === \"TableConstructorExpression\") {\n moduleTableNames.add(variable.name);\n }\n });\n }\n return moduleTableNames;\n}\n\n/**\n * @description Extracts one export symbol per field of a `return { ... }` table literal, the\n * other conventional Lua export idiom alongside the `local M = {}` module table.\n * @param topLevelStatements - Statements at the root of the chunk body.\n * @returns Export symbols named after each `TableKeyString` field in the trailing return table.\n */\nfunction collectReturnTableExports(topLevelStatements: Statement[]): ExportedSymbol[] {\n const lastStatement = topLevelStatements[topLevelStatements.length - 1];\n if (lastStatement?.type !== \"ReturnStatement\") return [];\n const returnedTable = lastStatement.arguments[0];\n if (returnedTable?.type !== \"TableConstructorExpression\") return [];\n\n const exportedSymbols: ExportedSymbol[] = [];\n for (const field of returnedTable.fields) {\n if (field.type === \"TableKeyString\") exportedSymbols.push({ name: field.key.name });\n }\n return exportedSymbols;\n}\n\n/**\n * @description Walks the top-level chunk statements to collect Lua module exports: fields\n * assigned onto a `local M = {}` module table (via `function M.foo()` or `M.foo = ...`),\n * fields of a trailing `return { ... }` table literal, and non-local top-level function\n * declarations (which are visible as Lua globals).\n * @param ast - The parsed luaparse AST root.\n * @returns One export symbol per discovered module member.\n */\nfunction collectExports(ast: Chunk): ExportedSymbol[] {\n const topLevelStatements = ast.body;\n const moduleTableNames = collectModuleTableNames(topLevelStatements);\n const exportedSymbols: ExportedSymbol[] = collectReturnTableExports(topLevelStatements);\n\n for (const statement of topLevelStatements) {\n if (statement.type === \"FunctionDeclaration\") {\n if (statement.identifier?.type === \"MemberExpression\") {\n if (\n statement.identifier.base.type === \"Identifier\" &&\n moduleTableNames.has(statement.identifier.base.name)\n ) {\n exportedSymbols.push({ name: statement.identifier.identifier.name });\n }\n } else if (statement.identifier?.type === \"Identifier\" && !statement.isLocal) {\n exportedSymbols.push({ name: statement.identifier.name });\n }\n } else if (statement.type === \"AssignmentStatement\") {\n for (const variable of statement.variables) {\n if (\n variable.type === \"MemberExpression\" &&\n variable.base.type === \"Identifier\" &&\n moduleTableNames.has(variable.base.name)\n ) {\n exportedSymbols.push({ name: variable.identifier.name });\n }\n }\n }\n }\n\n const seenNames = new Set<string>();\n return exportedSymbols.filter((symbol) => {\n if (seenNames.has(symbol.name)) return false;\n seenNames.add(symbol.name);\n return true;\n });\n}\n\n/**\n * @description Parses a Lua source file using luaparse to extract `require()` dependency edges,\n * module exports (the `local M = {}` / `return { ... }` idioms plus global function\n * declarations), and `@tag` comment annotations. Falls back to empty imports/exports if the\n * file contains 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, exports, extracted tags, and resolved category.\n */\nexport function parseLua(filePath: string, content: string): ParseResult {\n const tagNames = extractTagAnnotations(content);\n const category = classifyTestOrLogic(filePath, tagNames);\n\n let imports: ImportEdge[] = [];\n let exports: ExportedSymbol[] = [];\n try {\n const ast: Chunk = luaparse.parse(content);\n imports = collectRequireEdges(ast, filePath);\n exports = collectExports(ast);\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 Markdown/MDX files using remark to extract references to project files as import edges. */\nimport type { ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\n/** Minimal shape of an mdast node — avoids depending on `@types/mdast` for a handful of fields. */\ninterface MdastNode {\n type: string;\n url?: string;\n value?: string;\n children?: MdastNode[];\n}\n\nconst EXTERNAL_LINK_PREFIXES = [\"http://\", \"https://\", \"mailto:\", \"//\"];\n\nconst CODE_EXTENSIONS =\n \"ts|tsx|js|jsx|mjs|cjs|py|go|lua|css|scss|less|styl|coffee|ls|feature|md|mdx|json\";\nconst PATH_TOKEN_PATTERN = new RegExp(\n `(?:\\\\.{1,2}/)?[\\\\w.-]+(?:/[\\\\w.-]+)*\\\\.(?:${CODE_EXTENSIONS})\\\\b`,\n \"g\",\n);\n\nlet processorPromise: Promise<{ parse(content: string): MdastNode }> | undefined;\n\n/**\n * @description Lazily creates and caches a unified processor configured with `remark-parse`.\n * Both packages are ESM-only, so they're loaded via dynamic import from this CommonJS codebase.\n * @returns A processor whose synchronous `parse()` yields an mdast tree.\n */\nasync function getProcessor(): Promise<{ parse(content: string): MdastNode }> {\n processorPromise ??= (async () => {\n const { unified } = await import(\"unified\");\n const remarkParse = (await import(\"remark-parse\")).default;\n return unified().use(remarkParse) as unknown as { parse(content: string): MdastNode };\n })();\n return processorPromise;\n}\n\n/**\n * @description Returns true when a markdown link target points outside the project\n * (web URL, mailto, protocol-relative) or is a same-page anchor rather than a file reference.\n * @param url - The raw `url` field from an mdast `link` node.\n * @returns `true` if the link should be skipped rather than treated as a file reference.\n */\nfunction isExternalLink(url: string): boolean {\n const trimmed = url.trim();\n return (\n trimmed.length === 0 ||\n trimmed.startsWith(\"#\") ||\n EXTERNAL_LINK_PREFIXES.some((p) => trimmed.startsWith(p))\n );\n}\n\n/**\n * @description Scans the text of a code span or fenced code block for path-like tokens\n * (e.g. `src/auth/reset.ts`), since docs commonly reference files this way in prose.\n * @param text - Raw text content of an mdast `code` or `inlineCode` node.\n * @param filePath - Path of the markdown file being parsed, used as `fromPath` on each edge.\n * @returns One `ImportEdge` per distinct path-like token found.\n */\nfunction edgesFromCodeText(text: string, filePath: string): ImportEdge[] {\n const matches = text.match(PATH_TOKEN_PATTERN);\n if (!matches) return [];\n return matches.map((specifier) => ({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: false,\n type: \"static\" as const,\n }));\n}\n\n/**\n * @description Recursively walks an mdast tree collecting candidate file references from\n * `link` node URLs and `code`/`inlineCode` node text.\n * @param node - Current mdast node (root or any content node).\n * @param filePath - Path of the markdown file being parsed, used as `fromPath` on each edge.\n * @param edges - Accumulator array mutated in place.\n */\nfunction walk(node: MdastNode, filePath: string, edges: ImportEdge[]): void {\n if (node.type === \"link\" && typeof node.url === \"string\" && !isExternalLink(node.url)) {\n edges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: node.url,\n isStyle: false,\n type: \"static\",\n });\n }\n if ((node.type === \"code\" || node.type === \"inlineCode\") && typeof node.value === \"string\") {\n edges.push(...edgesFromCodeText(node.value, filePath));\n }\n if (Array.isArray(node.children)) {\n for (const child of node.children) walk(child, filePath, edges);\n }\n}\n\n/**\n * @description Parses a Markdown/MDX file into a `ParseResult` whose `imports` are candidate\n * references to project files (from links and code spans/blocks). Markdown has no export or\n * tag concept, so those arrays are always empty — mirroring the style-parser precedent.\n * @param filePath - Path of the markdown file being parsed.\n * @param content - Raw markdown source.\n * @returns A `ParseResult` with deduplicated import edges and `category: \"other\"`.\n */\nexport async function parseMarkdown(filePath: string, content: string): Promise<ParseResult> {\n const processor = await getProcessor();\n const tree = processor.parse(content);\n\n const edges: ImportEdge[] = [];\n walk(tree, filePath, edges);\n\n const seen = new Set<string>();\n const imports = edges.filter((edge) => {\n if (seen.has(edge.rawSpecifier)) return false;\n seen.add(edge.rawSpecifier);\n return true;\n });\n\n return { imports, exports: [], tags: [], category: \"other\" };\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, Tree } from \"@lezer/common\";\nimport { parser } from \"@lezer/python\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport { collectFunctionComplexity, computeComplexity } from \"../complexity/python\";\nimport type { ParseResult, RawCallEdge } 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 const { complexity, cognitiveComplexity } = computeComplexity(tree.topNode);\n const functions = collectFunctionComplexity(tree, content);\n const rawCallEdges = category === \"test\" ? [] : collectRawCallEdges(tree, content);\n\n return {\n imports,\n exports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n rawCallEdges,\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\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// ─── call-edge extraction ──────────────────────────────────────────────────────\n\n/**\n * @description Builds a map from each name bound by a `from <module> import <name> [as alias]`\n * statement to that module's raw specifier (mirroring the specifier computation in\n * `extractFromImport`, including alias resolution and relative-import prefixing). Bare\n * `import <module>` statements are not included: unqualified calls can't tell which bound\n * module a member access like `module.func()` belongs to, so — matching the TS parser's\n * documented exclusion of \"calls through chained member access\" — only directly named imports\n * are tracked as callable symbols.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {Map<string, string>} Local (possibly aliased) name → raw import specifier.\n */\nfunction buildImportSymbolMap(tree: Tree, content: string): Map<string, string> {\n const symbolMap = new Map<string, string>();\n const cursor = tree.cursor();\n\n do {\n if (cursor.name !== \"ImportStatement\") continue;\n const node = cursor.node;\n const fromKw = node.firstChild;\n if (fromKw?.type.name !== \"from\") continue;\n\n let importKw: SyntaxNode | null = fromKw.nextSibling;\n while (importKw && importKw.type.name !== \"import\") importKw = importKw.nextSibling;\n if (!importKw) continue;\n\n const rawModule = content.slice(fromKw.to, importKw.from).trim();\n let dotCount = 0;\n while (dotCount < rawModule.length && rawModule[dotCount] === \".\") dotCount++;\n const modulePart = rawModule.slice(dotCount);\n const prefix = dotCount <= 1 ? \"./\" : \"../\".repeat(dotCount - 1);\n\n let child: SyntaxNode | null = importKw.nextSibling;\n while (child) {\n if (child.type.name === \"VariableName\") {\n const importedName = content.slice(child.from, child.to);\n let localName = importedName;\n if (child.nextSibling?.type.name === \"as\") {\n const aliasNode = child.nextSibling.nextSibling;\n if (aliasNode?.type.name === \"VariableName\") {\n localName = content.slice(aliasNode.from, aliasNode.to);\n child = aliasNode;\n }\n }\n const specifier =\n dotCount === 0\n ? rawModule\n : modulePart\n ? prefix + modulePart.replace(/\\./g, \"/\")\n : prefix + importedName;\n symbolMap.set(localName, specifier);\n }\n child = child.nextSibling;\n }\n } while (cursor.next());\n\n return symbolMap;\n}\n\n/**\n * @description Walks every top-level `FunctionDefinition` and every method directly inside a\n * `ClassDefinition`'s body, recording a `RawCallEdge` for each bare call (`func(...)`) whose\n * callee resolves to a name bound by a `from <module> import <name>` statement. Methods are\n * qualified as `ClassName.methodName`, mirroring the TS parser's convention.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {RawCallEdge[]} One edge per call to a known imported symbol.\n */\nfunction collectRawCallEdges(tree: Tree, content: string): RawCallEdge[] {\n const importSymbols = buildImportSymbolMap(tree, content);\n const edges: RawCallEdge[] = [];\n\n function walkBody(node: SyntaxNode, callerName: string): void {\n if (node.type.name === \"CallExpression\" && node.firstChild?.type.name === \"VariableName\") {\n const calleeNode = node.firstChild;\n const calleeName = content.slice(calleeNode.from, calleeNode.to);\n const toSpecifier = importSymbols.get(calleeName);\n if (toSpecifier) edges.push({ from: callerName, to: calleeName, toSpecifier });\n }\n let child = node.firstChild;\n while (child) {\n walkBody(child, callerName);\n child = child.nextSibling;\n }\n }\n\n function walkChildren(node: SyntaxNode): void {\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n function walk(node: SyntaxNode): void {\n if (node.type.name === \"ClassDefinition\") {\n const classNameNode = node.getChild(\"VariableName\");\n const className = classNameNode\n ? content.slice(classNameNode.from, classNameNode.to)\n : undefined;\n const body = node.getChild(\"Body\");\n if (body) {\n let child = body.firstChild;\n while (child) {\n if (child.type.name === \"FunctionDefinition\") {\n const fnNameNode = child.getChild(\"VariableName\");\n const fnName = fnNameNode ? content.slice(fnNameNode.from, fnNameNode.to) : undefined;\n if (fnName) walkBody(child, className ? `${className}.${fnName}` : fnName);\n } else {\n walk(child);\n }\n child = child.nextSibling;\n }\n }\n return;\n }\n\n if (node.type.name === \"FunctionDefinition\") {\n const nameNode = node.getChild(\"VariableName\");\n if (nameNode) walkBody(node, content.slice(nameNode.from, nameNode.to));\n return;\n }\n\n walkChildren(node);\n }\n\n walk(tree.topNode);\n return edges;\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","/** Computes McCabe cyclomatic complexity and cognitive complexity for Python source, mirroring\n * the TypeScript algorithm in ../complexity.ts but walking the @lezer/python SyntaxNode tree.\n * Python's grammar represents `if`/`elif`/`else` and `try`/`except`/`else`/`finally` as flat\n * sibling sequences within a single node, unlike TS/Go's nested representation, so the\n * branch-chain walking logic differs from ../complexity.ts and ./go.ts even though the overall\n * scoring model (decision points, nesting-aware cognitive penalty) is the same. */\nimport type { SyntaxNode, Tree } from \"@lezer/common\";\nimport type { FunctionComplexity } from \"../../types/node\";\nimport { childrenOf, lineAt } from \"./lezer-utils\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for a Python AST node: every independent\n * decision point counts (base 1) — each `if`/`elif` branch, `for`, `while`, each `except`\n * clause, ternary (`ConditionalExpression`), and each `and`/`or` operator.\n * @param {SyntaxNode} rootNode - The AST root node to analyse — the whole file's top node for\n * file-level totals, or a `FunctionDefinition`/`LambdaExpression` node to score it alone.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: SyntaxNode): number {\n let complexity = 1;\n\n function walk(node: SyntaxNode): void {\n switch (node.type.name) {\n case \"IfStatement\": {\n complexity += childrenOf(node).filter(\n (k) => k.type.name === \"if\" || k.type.name === \"elif\",\n ).length;\n break;\n }\n case \"TryStatement\": {\n complexity += childrenOf(node).filter((k) => k.type.name === \"except\").length;\n break;\n }\n case \"ForStatement\":\n case \"WhileStatement\":\n case \"ConditionalExpression\":\n complexity++;\n break;\n case \"and\":\n case \"or\":\n complexity++;\n break;\n }\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for a Python\n * AST node, tracking how hard the code is to read by adding a nesting penalty. Since Python's\n * `if`/`elif`/`else` chain is one flat `IfStatement` node (not nested, unlike TS/Go), this walks\n * its direct children in groups: each fresh `if` adds `1 + depth` and nests its body; each\n * `elif` adds a flat +1 at the same depth as the original `if`; a bare `else` adds a flat +1 and\n * nests its body. `for`/`while` add `1 + depth` and nest their body. Each `except` clause adds\n * `1 + depth` without increasing nesting for its own body (mirroring how the TS parser scores\n * `catch`). Ternaries and `and`/`or` each add a flat +1. Nested `def`/`lambda` add `1 + depth`.\n * @param {SyntaxNode} rootNode - The AST root node to analyse (nesting depth resets to 0 here).\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: SyntaxNode): number {\n let cognitive = 0;\n\n function walk(node: SyntaxNode, depth: number): void {\n const name = node.type.name;\n\n if (name === \"IfStatement\") {\n const kids = childrenOf(node);\n let branchIndex = 0;\n let i = 0;\n while (i < kids.length) {\n const kw = kids[i];\n if (kw?.type.name === \"if\" || kw?.type.name === \"elif\") {\n const isElseIf = branchIndex > 0;\n cognitive += isElseIf ? 1 : 1 + depth;\n const bodyDepth = isElseIf ? depth : depth + 1;\n const cond = kids[i + 1];\n const body = kids[i + 2];\n if (cond) walk(cond, bodyDepth);\n if (body) walk(body, bodyDepth);\n branchIndex++;\n i += 3;\n } else if (kw?.type.name === \"else\") {\n cognitive += 1;\n const body = kids[i + 1];\n if (body) walk(body, depth + 1);\n i += 2;\n } else {\n i++;\n }\n }\n return;\n }\n\n if (name === \"TryStatement\") {\n const kids = childrenOf(node);\n let i = 0;\n while (i < kids.length) {\n const kw = kids[i];\n if (kw?.type.name === \"except\") {\n cognitive += 1 + depth;\n i++;\n while (i < kids.length && kids[i]?.type.name !== \"Body\") {\n walk(kids[i] as SyntaxNode, depth);\n i++;\n }\n if (i < kids.length) {\n walk(kids[i] as SyntaxNode, depth);\n i++;\n }\n } else if (kw?.type.name === \"Body\") {\n walk(kw, depth);\n i++;\n } else {\n i++;\n }\n }\n return;\n }\n\n if (name === \"ForStatement\" || name === \"WhileStatement\") {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1);\n child = child.nextSibling;\n }\n return;\n }\n\n if (name === \"ConditionalExpression\" || name === \"and\" || name === \"or\") {\n cognitive += 1;\n }\n\n const isNestedFunction =\n depth > 0 && (name === \"FunctionDefinition\" || name === \"LambdaExpression\");\n if (isNestedFunction) {\n cognitive += 1 + depth;\n let child = node.firstChild;\n while (child) {\n walk(child, depth + 1);\n child = child.nextSibling;\n }\n return;\n }\n\n let child = node.firstChild;\n while (child) {\n walk(child, depth);\n child = child.nextSibling;\n }\n }\n\n walk(rootNode, 0);\n return cognitive;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and cognitive complexity for a Python\n * AST node by composing `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {SyntaxNode} node - The AST root node to analyse.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores.\n */\nexport function computeComplexity(node: SyntaxNode): {\n complexity: number;\n cognitiveComplexity: number;\n} {\n return {\n complexity: computeCyclomaticComplexity(node),\n cognitiveComplexity: computeCognitiveComplexity(node),\n };\n}\n\n/**\n * @description Walks the entire Python source tree and records complexity for every named\n * `def`: top-level functions, and class methods qualified as `ClassName.methodName` (mirroring\n * the TS parser's convention). Functions nested inside another function or method are recorded\n * with their own bare name, unqualified — matching the TS parser, which never prefixes plain\n * nested function declarations with an enclosing class name either.\n * @param {Tree} tree - The parsed @lezer/python tree.\n * @param {string} content - Full source text.\n * @returns {FunctionComplexity[]} Per-function complexity entries, in traversal order.\n */\nexport function collectFunctionComplexity(tree: Tree, content: string): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n\n function recordFunction(node: SyntaxNode, name: string): void {\n const { complexity, cognitiveComplexity } = computeComplexity(node);\n results.push({ name, line: lineAt(content, node.from), complexity, cognitiveComplexity });\n }\n\n function walkChildren(node: SyntaxNode): void {\n let child = node.firstChild;\n while (child) {\n walk(child);\n child = child.nextSibling;\n }\n }\n\n function walk(node: SyntaxNode): void {\n if (node.type.name === \"ClassDefinition\") {\n const classNameNode = node.getChild(\"VariableName\");\n const className = classNameNode\n ? content.slice(classNameNode.from, classNameNode.to)\n : undefined;\n const body = node.getChild(\"Body\");\n if (body) {\n let child = body.firstChild;\n while (child) {\n if (child.type.name === \"FunctionDefinition\") {\n const fnNameNode = child.getChild(\"VariableName\");\n const fnName = fnNameNode ? content.slice(fnNameNode.from, fnNameNode.to) : undefined;\n if (fnName) recordFunction(child, className ? `${className}.${fnName}` : fnName);\n walkChildren(child);\n } else {\n walk(child);\n }\n child = child.nextSibling;\n }\n }\n return;\n }\n\n if (node.type.name === \"FunctionDefinition\") {\n const nameNode = node.getChild(\"VariableName\");\n if (nameNode) recordFunction(node, content.slice(nameNode.from, nameNode.to));\n walkChildren(node);\n return;\n }\n\n walkChildren(node);\n }\n\n walk(tree.topNode);\n return results;\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, stmt.name.text, 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 {string} className - The class's name, pre-extracted by the caller (which already\n * narrowed `classDecl.name` to non-null before calling in).\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 className: string,\n importSymbolMap: Map<string, string>,\n edges: RawCallEdge[],\n): void {\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 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\n * standard shape. SCSS and Less populate `exports`/`tags` from their root-level variable/mixin/function\n * declarations (see `parseScssContent`, `parseLessContent`); CSS and Stylus have no equivalent module\n * surface today, so they always report empty `exports`/`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, exports/tags (SCSS/Less only), 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, exports, tags } = parseScssContent(content, filePath);\n return { imports, exports, tags, category: detectCssBarrel(root, imports) };\n }\n\n if (fileType === \"less\") {\n const { imports, root, exports, tags } = 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 { parseMarkdown } from \"./parser/lang/markdown\";\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 [\"markdown\", parseMarkdown],\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 *resolved* import edges and appends a structured `import`-kind tag\n * for every third-party library found. Scoped packages (`@scope/pkg/deep`) are normalised to their\n * two-segment name before deduplication. Relies on `isExternal` (set by the resolver) rather than\n * specifier syntax, since some languages — notably Sass/Less/Stylus — use bare specifiers\n * (`@import 'variables'`) to mean a local sibling file, not a package.\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.isExternal) {\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\n/**\n * @description Links markdown docs to the code files they reference and flags likely-stale docs.\n * For each markdown node's resolved local imports, records the doc on the target's `documentedBy`\n * (any target, so \"what docs reference this file?\" stays accurate regardless of category), and —\n * when the target's last commit is newer than the doc's own — records the target on the doc's\n * `staleFor`. The staleness check is scoped to `category: \"logic\"` targets only: barrels (e.g.\n * `index.ts`), config files, and non-code files (package.json, lockfiles, other markdown) churn\n * constantly for reasons unrelated to documented behavior — comparing against them made every doc\n * in a repo look perpetually stale. This is still a commit-recency heuristic, not a content diff:\n * a doc can go stale without any timestamp gap (e.g. a behavior change bundled into the same\n * commit as its docs update), and a logic file touched only for formatting will still flag any\n * doc referencing it. Treat `staleFor` as \"worth a look\", not \"definitely wrong\".\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichDocDrift(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.type !== \"markdown\") 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 || target === node) continue;\n\n target.documentedBy ??= [];\n if (!target.documentedBy.includes(node.path)) target.documentedBy.push(node.path);\n\n if (target.category !== \"logic\") continue;\n if ((target.lastCommitAt ?? 0) > (node.lastCommitAt ?? 0)) {\n node.staleFor ??= [];\n if (!node.staleFor.includes(target.path)) node.staleFor.push(target.path);\n }\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 MarkdownLangResolver,\n PythonLangResolver,\n type ResolvedImport,\n StyleLangResolver,\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 * Explicit path-alias map (same shape as tsconfig's `compilerOptions.paths`), e.g.\n * `{ \"@app/*\": [\"src/app/*\"] }`. Checked before `tsconfig.json`-derived aliases, so entries\n * here take precedence — useful for JS-only projects without a tsconfig, Vite/webpack\n * alias configs, or overriding what a tsconfig declares. Substitution paths are resolved\n * relative to `rootDir`.\n */\n pathAliases?: Record<string, string[]> | undefined;\n /**\n * Language-specific resolvers that handle bare (non-relative) specifiers before\n * falling through to the external-module default. Defaults to Python, Lua, Go, and style 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 pathAliases: Record<string, string[]> | undefined;\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.pathAliases = options.pathAliases;\n this.langResolvers = options.langResolvers ?? [\n new PythonLangResolver(),\n new LuaLangResolver(),\n new GoLangResolver(),\n new StyleLangResolver(),\n new MarkdownLangResolver(),\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 \".md\",\n \".mdx\",\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 Matches the specifier against the explicit `pathAliases` config option\n * (if set), then falls back to `tsconfig.json`-derived `compilerOptions.paths` aliases,\n * trying each substitution 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 if (this.pathAliases) {\n for (const alias in this.pathAliases) {\n const match = this.matchAliasPattern(alias, specifier);\n const substitutions = this.pathAliases[alias];\n if (match && substitutions) {\n const resolved = this.tryAliasSubstitutions(substitutions, match[1] || \"\");\n if (resolved) return resolved;\n }\n }\n }\n\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 Markdown: resolves bare doc-style paths (e.g. `src/graph/builder.ts`), the project-root-relative convention used throughout mokosh's own docs, since those don't start with `.` or `/` and would otherwise fall through to \"external\". */\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\nexport class MarkdownLangResolver implements LangResolver {\n extensions = [\".md\", \".mdx\"];\n\n /**\n * @description Treats a bare specifier as relative to the project root rather than the\n * markdown file's own directory, by probing it against a dummy file inside `rootDir`.\n * @param {string} _currentFile - Absolute path of the markdown file (unused; resolution is root-relative).\n * @param {string} specifier - The raw bare specifier extracted from a link or code span, e.g. `\"src/graph/builder.ts\"`.\n * @param {string} rootDir - Absolute project root directory.\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport[] | null} The resolved local file, or `null` to fall through to external.\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 resolved = resolveLocal(path.join(rootDir, \"_dummy\"), `./${specifier}`);\n return resolved ? [resolved] : 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","/** Language resolver for style dialects: resolves bare `@import`/`@require`/`@use` specifiers relative to the importing file. */\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\nconst EXTERNAL_PREFIXES = [\"~\", \"http://\", \"https://\", \"//\", \"data:\", \"sass:\"];\n\n/**\n * @description Resolves bare style import specifiers (e.g. `@import 'variables'` in Sass/Less/Stylus,\n * which by convention means \"same directory\", unlike JS bare specifiers which mean \"node_modules\").\n * Also tries the Sass/SCSS partial-file convention (`variables` → `_variables.scss`).\n */\nexport class StyleLangResolver implements LangResolver {\n extensions = [\".css\", \".scss\", \".sass\", \".less\", \".styl\"];\n\n /**\n * @description Treats a bare specifier as relative to the importing file's directory, then\n * falls back to the Sass/SCSS underscore-partial convention before giving up.\n * @param {string} currentFile - Absolute path of the importing style file.\n * @param {string} specifier - The raw bare specifier from source, e.g. `\"variables\"`.\n * @param {string} _rootDir - Absolute project root (unused; resolution is directory-relative).\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport[] | null} The resolved local file, or `null` to fall through to external.\n */\n resolve(\n currentFile: string,\n specifier: string,\n _rootDir: string,\n resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n if (EXTERNAL_PREFIXES.some((prefix) => specifier.startsWith(prefix))) return null;\n\n const resolved = resolveLocal(currentFile, specifier);\n if (resolved) return [resolved];\n\n const dir = path.dirname(specifier);\n const base = path.basename(specifier);\n const partialSpecifier = dir === \".\" ? `_${base}` : `${dir}/_${base}`;\n const partialResolved = resolveLocal(currentFile, partialSpecifier);\n if (partialResolved) return [partialResolved];\n\n return null;\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, configToGraphOptions, 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 type { ParallelParsingOption } from \"./graph/builder\";\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 type DuplicateFamily,\n type DuplicateGroup,\n type DuplicateOccurrence,\n type FindDuplicatesOptions,\n findDuplicates,\n} from \"./graph/duplication\";\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\";\nexport {\n CALL_EDGE_TYPES,\n EXPORT_TRACKING_TYPES,\n getLanguageCoverage,\n IMPORT_SYMBOL_TYPES,\n type LanguageCoverage,\n} from \"./graph/language-support\";\n// Core graph classes\nexport { Graph } from \"./graph/model\";\nexport {\n type CallerEntry as GraphCallerEntry,\n type ComplexFunctionEntry,\n type FindComplexFunctionsOptions,\n findComplexFunctions,\n type GetAffectedOptions,\n type GetCallersOptions,\n getAffected,\n getCallers,\n getDependencies,\n getDependents,\n getNodeMeta,\n hasCoverageData,\n type NodeMeta,\n type PathWithSymbols,\n type SlimNode,\n type SlimSerializedGraph,\n slimSerialize,\n summarizeWorkspacePackages,\n type WorkspacePackageSummary,\n type WorkspacePackagesSummary,\n} from \"./graph/queries\";\nexport { buildResponsibilityGraph } from \"./graph/responsibility\";\nexport type {\n ModuleResponsibility,\n ModuleRole,\n ResponsibilityGraph,\n} from \"./graph/responsibility/types\";\nexport {\n findSymbol,\n type SymbolCaller,\n type SymbolImporter,\n type SymbolMatch,\n type SymbolPrecision,\n} from \"./graph/symbol\";\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 {\n DefaultResolver,\n detectMonorepo,\n type Graph,\n GraphBuilder,\n type ParallelParsingOption,\n WorkspaceGraph,\n} 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; `parallelParsing` controls worker-pool offloading of file parsing (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution (see `MokoshConfig.pathAliases`).\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: {\n silent?: boolean;\n gitStats?: boolean;\n coverageMap?: Map<string, number>;\n parallelParsing?: ParallelParsingOption | undefined;\n pathAliases?: Record<string, string[]> | undefined;\n } = {},\n): Promise<Graph> {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`Processed ${count} files...\\r`);\n };\n const abs = path.resolve(rootDir);\n const builder = new GraphBuilder(\n abs,\n previousGraph,\n options.pathAliases\n ? new DefaultResolver(abs, { pathAliases: options.pathAliases })\n : undefined,\n progressCallback,\n options.gitStats ?? false,\n options.coverageMap ?? new Map(),\n options.parallelParsing ?? true,\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; `parallelParsing` controls worker-pool offloading of file parsing per package (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution for every package (see `MokoshConfig.pathAliases`).\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: {\n packages?: string[];\n silent?: boolean;\n gitStats?: boolean;\n parallelParsing?: ParallelParsingOption | undefined;\n pathAliases?: Record<string, string[]> | undefined;\n } = {},\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, {\n workspaceMap,\n tsconfigSearchPaths: [pkg.root, abs],\n pathAliases: options.pathAliases,\n }),\n progressCallback,\n options.gitStats ?? false,\n new Map(),\n options.parallelParsing ?? true,\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 checkDocDrift: boolean;\n findUncovered: boolean;\n listTags: 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 findSymbol: boolean;\n functionName: string | undefined;\n apiSurface: boolean;\n applyTags: boolean;\n dryRun: boolean;\n initSkill: boolean;\n initConfig: boolean;\n force: boolean;\n dependencies: boolean;\n dependents: boolean;\n affected: boolean;\n testsOnly: boolean;\n changedSymbols: string[] | undefined;\n cached: boolean;\n depth: number | undefined;\n withMeta: boolean;\n withEdgeDetail: boolean;\n findComplexFunctions: boolean;\n metric: \"cognitiveComplexity\" | \"complexity\" | undefined;\n complexityThreshold: number | undefined;\n limit: number | undefined;\n findDuplicates: boolean;\n minDuplicateLines: number | undefined;\n workspacePackages: boolean;\n workspaceAffected: boolean;\n clearCache: boolean;\n slim: boolean;\n watch: 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 a raw numeric CLI value, returning `undefined` for missing/empty input.\n * @param {string | undefined} raw - The raw string value from `nodeParseArgs`, if provided.\n * @returns {number | undefined} The parsed integer, or `undefined` if `raw` is falsy.\n */\nfunction parseOptionalInt(raw: string | undefined): number | undefined {\n return raw ? parseInt(raw, 10) : undefined;\n}\n\n/**\n * @description Parses a raw comma-separated CLI value into a trimmed string array.\n * @param {string | undefined} raw - The raw comma-separated string from `nodeParseArgs`, if provided.\n * @returns {string[] | undefined} The trimmed parts, or `undefined` if `raw` is falsy.\n */\nfunction parseCsv(raw: string | undefined): string[] | undefined {\n return raw ? raw.split(\",\").map((part) => part.trim()) : undefined;\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 const 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 \"check-doc-drift\": { type: \"boolean\" },\n \"find-uncovered\": { type: \"boolean\" },\n \"list-tags\": { 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 \"find-symbol\": { type: \"boolean\" },\n \"api-surface\": { type: \"boolean\" },\n \"apply-tags\": { type: \"boolean\" },\n \"dry-run\": { type: \"boolean\" },\n \"init-skill\": { type: \"boolean\" },\n \"init-config\": { type: \"boolean\" },\n force: { type: \"boolean\" },\n dependencies: { type: \"boolean\" },\n dependents: { type: \"boolean\" },\n affected: { type: \"boolean\" },\n \"tests-only\": { type: \"boolean\" },\n \"changed-symbols\": { type: \"string\" },\n cached: { type: \"boolean\" },\n depth: { type: \"string\" },\n \"with-meta\": { type: \"boolean\" },\n \"with-edge-detail\": { type: \"boolean\" },\n \"find-complex-functions\": { type: \"boolean\" },\n metric: { type: \"string\" },\n \"complexity-threshold\": { type: \"string\" },\n limit: { type: \"string\" },\n \"find-duplicates\": { type: \"boolean\" },\n \"min-duplicate-lines\": { type: \"string\" },\n \"workspace-packages\": { type: \"boolean\" },\n \"workspace-affected\": { type: \"boolean\" },\n \"clear-cache\": { type: \"boolean\" },\n slim: { type: \"boolean\" },\n watch: { 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 === undefined) continue;\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 cacheValue = values.cache;\n const configValue = values.config;\n const metricRaw = values.metric;\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: parseCsv(values.paths),\n featureThreshold: parseOptionalInt(values[\"feature-threshold\"]),\n minOutDegree: parseOptionalInt(values[\"min-out-degree\"]),\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 checkDocDrift: values[\"check-doc-drift\"] ?? false,\n findUncovered: values[\"find-uncovered\"] ?? false,\n listTags: values[\"list-tags\"] ?? 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 findSymbol: values[\"find-symbol\"] ?? false,\n apiSurface: values[\"api-surface\"] ?? false,\n applyTags: values[\"apply-tags\"] ?? false,\n dryRun: values[\"dry-run\"] ?? false,\n initSkill: values[\"init-skill\"] ?? false,\n initConfig: values[\"init-config\"] ?? false,\n force: values.force ?? false,\n dependencies: values.dependencies ?? false,\n dependents: values.dependents ?? false,\n affected: values.affected ?? false,\n testsOnly: values[\"tests-only\"] ?? false,\n changedSymbols: parseCsv(values[\"changed-symbols\"]),\n cached: values.cached ?? false,\n depth: parseOptionalInt(values.depth),\n withMeta: values[\"with-meta\"] ?? false,\n withEdgeDetail: values[\"with-edge-detail\"] ?? false,\n findComplexFunctions: values[\"find-complex-functions\"] ?? false,\n metric:\n metricRaw === \"complexity\" || metricRaw === \"cognitiveComplexity\" ? metricRaw : undefined,\n complexityThreshold: parseOptionalInt(values[\"complexity-threshold\"]),\n limit: parseOptionalInt(values.limit),\n findDuplicates: values[\"find-duplicates\"] ?? false,\n minDuplicateLines: parseOptionalInt(values[\"min-duplicate-lines\"]),\n workspacePackages: values[\"workspace-packages\"] ?? false,\n workspaceAffected: values[\"workspace-affected\"] ?? false,\n clearCache: values[\"clear-cache\"] ?? false,\n slim: values.slim ?? false,\n watch: values.watch ?? false,\n entryPoints: positionals,\n };\n}\n","export const DEFAULT_CACHE_DIR = \"mokosh-cache\";\nexport const DEFAULT_CACHE_FILE = \"graph.json\";\n","/** CLI command: prints all files transitively affected if a given file changes, mirroring the MCP get_affected tool. */\nimport { buildChangeImpactCache, getAffected, getNodeMeta, queryChangeImpact } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints every file transitively affected if `--file` changes (full incoming\n * traversal). Set `--tests-only` to restrict to test/spec files, `--changed-symbols` to\n * restrict propagation to files that import those specific symbols, `--cached` to use\n * a precomputed impact cache instead of a fresh traversal, and `--with-meta` to turn each\n * affected path into `{ path, category, exports }`.\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, testsOnly, changedSymbols, cached, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --affected requires --file <path>\");\n process.exit(1);\n }\n\n const annotate = (paths: string[]) =>\n withMeta ? paths.map((p) => ({ path: p, ...getNodeMeta(graph, p) })) : paths;\n\n if (cached) {\n const impactCache = buildChangeImpactCache(graph);\n const allAffected = queryChangeImpact(impactCache, file);\n const affected = testsOnly\n ? allAffected.filter((filePath) => graph.nodes.get(filePath)?.category === \"test\")\n : allAffected;\n const result = annotate(affected);\n console.log(JSON.stringify({ file, affected: result, count: result.length }, null, 2));\n return;\n }\n\n const affected = getAffected(graph, file, { testsOnly, changedSymbols });\n const result = annotate(affected);\n console.log(JSON.stringify({ file, affected: result, count: result.length }, null, 2));\n}\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 {\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n proposeAffectedTests,\n} 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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, configToGraphOptions, 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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 { getCallers } from \"../../index\";\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, depth, withEdgeDetail } = ctx;\n\n if (!file) {\n console.error(\"Error: --callers requires --file <path>\");\n process.exit(1);\n }\n\n const callers = getCallers(graph, file, { depth: depth ?? 1, withEdgeDetail });\n\n if (plain) {\n console.log(callers.map((caller) => caller.file).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: flags markdown docs whose referenced files changed more recently than the doc itself. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans markdown nodes for `staleFor` entries (populated by `enrichDocDrift`) and\n * prints each stale doc alongside the files that outpaced it. Exits with code 1 if any are\n * found; otherwise confirms a clean state to stdout. This is a commit-recency heuristic, not\n * a content diff — see `docs/adr-009-markdown-parsing.md` for the known limitations.\n * Requires the graph to have been built with git stats enabled (`--git-stats` or config\n * equivalent); otherwise every doc has no `lastCommitAt` data and nothing is flagged.\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 staleDocs = [...graph.nodes.values()]\n .filter((node) => node.type === \"markdown\" && node.staleFor && node.staleFor.length > 0)\n .map((node) => ({ doc: node.path, staleFor: node.staleFor as string[] }));\n\n if (staleDocs.length > 0) {\n process.stderr.write(`Found ${staleDocs.length} doc(s) that may be out of date:\\n`);\n for (const { doc, staleFor } of staleDocs) {\n process.stderr.write(` ${doc} — references changed more recently: ${staleFor.join(\", \")}\\n`);\n }\n process.exit(1);\n }\n console.log(\"No doc drift detected.\");\n}\n","/** CLI command: deletes the disk graph cache file, mirroring the MCP clear_cache tool. */\nimport fs from \"node:fs\";\n\n/**\n * @description Deletes the resolved graph cache file, if present, so the next run rebuilds from scratch.\n * @param {string} cachePath - Absolute path to the cache file (resolved from `--cache` or the default).\n */\nexport function runClearCache(cachePath: string): void {\n if (fs.existsSync(cachePath)) {\n fs.unlinkSync(cachePath);\n console.log(`Cache cleared: ${cachePath}`);\n } else {\n console.log(`No cache file present at ${cachePath}`);\n }\n}\n","/** CLI command: prints files that a given file imports (outgoing traversal), mirroring the MCP get_dependencies tool. */\nimport { getDependencies, getNodeMeta } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints files reachable from `--file` via outgoing import edges, up to `--depth` hops.\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, depth, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --dependencies requires --file <path>\");\n process.exit(1);\n }\n\n const deps = getDependencies(graph, file, depth ?? 1);\n const dependencies = withMeta\n ? deps.map((dep) => ({ ...dep, ...getNodeMeta(graph, dep.path) }))\n : deps;\n console.log(JSON.stringify({ file, dependencies }, null, 2));\n}\n","/** CLI command: prints files that directly import a given file (one-hop incoming), mirroring the MCP get_dependents tool. */\nimport { getDependents, getNodeMeta } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints files that directly import `--file`.\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, withMeta } = ctx;\n\n if (!file) {\n console.error(\"Error: --dependents requires --file <path>\");\n process.exit(1);\n }\n\n const deps = getDependents(graph, file);\n const dependents = withMeta\n ? deps.map((dep) => ({ ...dep, ...getNodeMeta(graph, dep.path) }))\n : deps;\n console.log(JSON.stringify({ file, dependents }, null, 2));\n}\n","/** CLI command: detects feature-hub files and prints them as JSON. */\nimport {\n configToGraphOptions,\n createImportMap,\n detectFeatures,\n getAllProjectFiles,\n} 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, rawConfig } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph, configToGraphOptions(rawConfig));\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 {\n buildFeatureGraph,\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n} 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, rawConfig } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph, configToGraphOptions(rawConfig));\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 functions/methods at or above a complexity threshold, mirroring the MCP find_complex_functions tool. */\nimport { findComplexFunctions } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans every file's per-function complexity breakdown and prints functions/methods\n * at or above `--complexity-threshold`, sorted worst-first. Populated for TypeScript/JavaScript,\n * Go, and Python.\n * @param {CommandContext} ctx - Shared command context; `ctx.metric`, `ctx.complexityThreshold`, and `ctx.limit` tune the scan.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, metric, complexityThreshold, limit } = ctx;\n const threshold = complexityThreshold ?? 10;\n const functions = findComplexFunctions(graph, { metric, threshold, limit });\n console.log(\n JSON.stringify(\n { metric: metric ?? \"cognitiveComplexity\", threshold, functions, count: functions.length },\n null,\n 2,\n ),\n );\n}\n","/** CLI command: lists duplicated code blocks across the project, mirroring the MCP find_duplicates tool. */\nimport { DEFAULT_IGNORE_DIRS, findDuplicates } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans every file in the graph for cross-file (and within-file) duplicated code\n * and prints the resulting blocks, largest-first. Works for every language mokosh parses —\n * the underlying detector is token-based, not AST-based, so it doesn't depend on per-language\n * parser support. Lock files and files under an ignored directory (project config's\n * `ignoreDirs`, merged with the defaults) are excluded even when the graph itself contains\n * them — `graph.nodes` isn't ignore-rule-filtered for files reached via a resolved reference\n * rather than the initial FS walk.\n * @param {CommandContext} ctx - Shared command context; `ctx.rootDir`, `ctx.scanOptions`,\n * `ctx.minDuplicateLines`, and `ctx.limit` tune the scan.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, rootDir, scanOptions, minDuplicateLines, limit } = ctx;\n const minLines = minDuplicateLines ?? 6;\n const ignoreDirs = [\n ...(scanOptions.ignoreDirs ?? DEFAULT_IGNORE_DIRS),\n ...(scanOptions.additionalIgnoreDirs ?? []),\n ];\n const { groups, skippedBuckets } = await findDuplicates(graph, rootDir, {\n minLines,\n limit,\n ignoreDirs,\n });\n console.log(JSON.stringify({ minLines, groups, count: groups.length, skippedBuckets }, null, 2));\n}\n","/** CLI command: finds every file that exports a symbol by name. */\nimport { findSymbol } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Finds every file that exports `functionName` (reused as the symbol name),\n * with the best available usage info per match — call-edge callers for TS/JS, named-import\n * tracking for Python, whole-file dependents otherwise.\n * @param {CommandContext} ctx - Shared command context; `ctx.functionName` must be set via `--function`.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, functionName, plain } = ctx;\n\n if (!functionName) {\n console.error(\"Error: --find-symbol requires --function <name>\");\n process.exit(1);\n }\n\n const matches = findSymbol(graph, functionName);\n\n if (plain) {\n console.log(matches.map((match) => match.path).join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ name: functionName, matches, count: matches.length }, null, 2));\n }\n}\n","/** CLI command: lists non-test files below the configured coverage threshold. */\nimport { hasCoverageData } from \"../../index\";\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 * if no coverage data was ever loaded, prints an error instead of misreporting every file as uncovered.\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 if (!hasCoverageData(graph)) {\n console.log(\n JSON.stringify(\n {\n error:\n \"No coverage data available. Set coverageReportPath in mokosh.config and rebuild the graph.\",\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n const uncovered = [...graph.nodes.values()]\n .filter((node) => node.category !== \"test\" && node.category !== \"config\")\n .filter((node) => node.coveragePct !== undefined && node.coveragePct < threshold)\n .map((node) => ({ file: node.path, coveragePct: node.coveragePct as number }));\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 {\n filterGraph,\n Graph,\n getLanguageCoverage,\n MermaidExporter,\n parseQuery,\n slimSerialize,\n} 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 * Pass `--slim` for a compact response (export names, meaningful tags, flat importsFiles list) —\n * matching the MCP `query` tool's default shape; the CLI defaults to the full shape (`--slim` false)\n * to preserve existing script compatibility.\n * @param {CommandContext} ctx - Command context carrying the built graph, an optional query string, and the mermaid/slim output flags.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, queryStr, mermaidOutput, slim } = 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 return;\n }\n\n const cycles = graph.findCycles();\n if (cycles.length > 0) {\n serialized.cycles = cycles;\n }\n const languageCoverage = getLanguageCoverage(graph);\n\n if (slim) {\n console.log(JSON.stringify({ ...slimSerialize(serialized), languageCoverage }, null, 2));\n } else {\n console.log(JSON.stringify({ ...serialized, languageCoverage }, null, 2));\n }\n}\n","/** CLI command: scaffolds a starter mokosh.config.js into the current project. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findTemplateDir } from \"./template-dir\";\n\n/**\n * @description Copies the bundled `mokosh.config.js` template into the project root, so a\n * user gets a commented starter config instead of hand-writing one from docs. Existing\n * files are left untouched unless `force` is passed. The MCP server only reads\n * `mokosh.config.json` (JS execution is disabled there), so this scaffolds a `.js` file\n * aimed at CLI/programmatic use where comments and factory functions are supported.\n * @param {boolean} force - When true, overwrite the file if it already exists.\n * @param {string} cwd - Project directory to scaffold into (default: `process.cwd()`).\n */\nexport function runInitConfig(force: boolean, cwd: string = process.cwd()): void {\n const templateDir = findTemplateDir(__dirname, [\"config\"], \"mokosh.config.js\");\n const src = path.join(templateDir, \"mokosh.config.js\");\n const dest = path.join(cwd, \"mokosh.config.js\");\n\n if (fs.existsSync(dest) && !force) {\n console.log(\"skipped mokosh.config.js (already exists, use --force to overwrite)\");\n return;\n }\n\n fs.copyFileSync(src, dest);\n console.log(\"wrote mokosh.config.js\");\n}\n","/** Shared helper for locating bundled `templates/` subdirectories from a compiled or source layout. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * @description Locates a bundled `templates/<subpath>` directory by walking up from `startDir`,\n * checking for `markerFile` at each level. Templates live one level above the compiled\n * `dist/cli.js` in the published package, but several levels above `src/cli/commands/` when\n * running against source (tests, ts-node) — walking up avoids hardcoding either layout.\n * @param {string} startDir - Directory to start searching from (typically `__dirname`).\n * @param {string[]} subpathSegments - Path segments under `templates/` to locate, e.g. `[\"skill\"]`.\n * @param {string} markerFile - Filename within the target directory used to confirm it was found.\n * @returns {string} Absolute path to the located `templates/<subpath>` directory.\n */\nexport function findTemplateDir(\n startDir: string,\n subpathSegments: string[],\n markerFile: string,\n): string {\n let dir = startDir;\n for (let i = 0; i < 6; i++) {\n const candidate = path.join(dir, \"templates\", ...subpathSegments);\n if (fs.existsSync(path.join(candidate, markerFile))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(`Could not locate mokosh templates/${subpathSegments.join(\"/\")}`);\n}\n","/** CLI command: scaffolds the bundled mokosh Claude Code skill/command into the current project. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findTemplateDir } from \"./template-dir\";\n\ninterface Target {\n templateFile: string;\n destPath: string[];\n}\n\nconst TARGETS: Target[] = [\n { templateFile: \"SKILL.md\", destPath: [\".claude\", \"skills\", \"mokosh\", \"SKILL.md\"] },\n { templateFile: \"mokosh.md\", destPath: [\".claude\", \"commands\", \"mokosh.md\"] },\n];\n\n/**\n * @description Copies the bundled mokosh skill (`SKILL.md`) and slash command (`mokosh.md`)\n * templates into the target project's `.claude/` directory, so a downstream project's\n * Claude Code picks up guidance on driving mokosh via MCP or CLI. Existing files are left\n * untouched unless `force` is passed.\n * @param {boolean} force - When true, overwrite files that already exist at the destination.\n * @param {string} cwd - Project directory to scaffold into (default: `process.cwd()`).\n */\nexport function runInitSkill(force: boolean, cwd: string = process.cwd()): void {\n const templateDir = findTemplateDir(__dirname, [\"skill\"], \"SKILL.md\");\n\n for (const target of TARGETS) {\n const src = path.join(templateDir, target.templateFile);\n const dest = path.join(cwd, ...target.destPath);\n const relDest = path.join(...target.destPath);\n\n if (fs.existsSync(dest) && !force) {\n console.log(`skipped ${relDest} (already exists, use --force to overwrite)`);\n continue;\n }\n\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.copyFileSync(src, dest);\n console.log(`wrote ${relDest}`);\n }\n}\n","/** CLI command: lists every distinct tag in the graph with its per-node count. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Counts how many nodes carry each distinct tag name across the graph and prints\n * them sorted by count descending. Lets you check what tags exist before filtering with\n * `--query \"tag:<name>\"` — mirrors the MCP `list_tags` tool. Includes all tag kinds, not just\n * the subset kept by `--query`'s slim output.\n * @param {CommandContext} ctx - Shared command context carrying the built graph.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, plain } = ctx;\n\n const counts = new Map<string, number>();\n for (const node of graph.nodes.values()) {\n for (const tag of node.tags) {\n counts.set(tag.name, (counts.get(tag.name) ?? 0) + 1);\n }\n }\n const tags = [...counts.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((a, b) => b.count - a.count);\n\n if (plain) {\n console.log(tags.map((tag) => tag.name).join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ tags, count: tags.length }, 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 {\n configToGraphOptions,\n createImportMap,\n getAllProjectFiles,\n proposeTags,\n} 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, rawConfig } = 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(\n rootDir,\n getTestFiles(allFiles),\n graph,\n configToGraphOptions(rawConfig),\n );\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","/** CLI command: cross-package blast-radius analysis, mirroring the MCP get_workspace_affected tool. */\nimport type { WorkspaceGraph } from \"../../index\";\n\n/**\n * @description Prints every file (annotated with its package) that could be affected if `file` changes.\n * @param {WorkspaceGraph} wg - The workspace graph built for the monorepo root.\n * @param {string} file - Monorepo-root-relative path of the changed file.\n */\nexport function runWorkspaceAffected(wg: WorkspaceGraph, file: string): void {\n const affected = wg.getAffectedAcrossPackages(file);\n console.log(JSON.stringify({ file, affected, count: affected.length }, null, 2));\n}\n","/** CLI command: lists monorepo packages and their cross-package dependencies, mirroring the MCP get_workspace_packages tool. */\nimport { summarizeWorkspacePackages, type WorkspaceGraph } from \"../../index\";\n\n/**\n * @description Prints every workspace package detected in a monorepo, with node counts and\n * cross-package dependencies.\n * @param {WorkspaceGraph} wg - The workspace graph built for the monorepo root.\n */\nexport function runWorkspacePackages(wg: WorkspaceGraph): void {\n console.log(JSON.stringify(summarizeWorkspacePackages(wg), null, 2));\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\";\nimport { DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE } from \"./const\";\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 Picks the effective cache path: an explicit CLI override wins, then the\n * config-file value (resolved against rootDir), then the computed default.\n * @param {string} cachePath - The CLI-resolved cache path (already defaulted if unset).\n * @param {string} defaultCachePath - The computed default cache path to compare against.\n * @param {string | undefined} configCachePath - The cache path from mokosh.config.*, if any.\n * @param {string} rootDir - The project root used to resolve a relative configCachePath.\n * @returns {string} The effective absolute cache path.\n */\nfunction resolveCachePath(\n cachePath: string,\n defaultCachePath: string,\n configCachePath: string | undefined,\n rootDir: string,\n): string {\n if (cachePath !== defaultCachePath) return cachePath;\n if (configCachePath) return path.resolve(rootDir, configCachePath);\n return defaultCachePath;\n}\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, DEFAULT_CACHE_DIR), DEFAULT_CACHE_FILE);\n\n const resolvedEntryPoints = entryPoints.length > 0 ? entryPoints : (config.entryPoints ?? []);\n\n const resolvedCachePath = resolveCachePath(\n cachePath,\n defaultCachePath,\n config.cachePath,\n rootDir,\n );\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 { configToGraphOptions, 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 {object} [options] - `silent` suppresses progress output; the rest is the graph-build subset of `MokoshConfig` — build via {@link configToGraphOptions}.\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 options: { silent?: boolean } & ReturnType<typeof configToGraphOptions> = {\n silent: false,\n ...configToGraphOptions(undefined),\n },\n): Promise<Graph> {\n return createImportMap(rootDir, entryPoints, cachedGraph, options);\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 --find-uncovered List non-test files whose coverage is below the threshold (requires coverageReportPath in config)\n --list-tags List every distinct tag in the graph with its per-node count, sorted\n by count descending (use with --plain for a bare name list)\n --callers List files whose exported functions call into --file\n --file <path> Target file for --callers/--dependencies/--dependents/--affected/--workspace-affected\n --check-cycles Check for circular dependencies; exits non-zero if found (CI gate)\n --check-doc-drift Flag markdown docs whose referenced files changed more recently\n than the doc itself; exits non-zero if found (CI gate; requires\n gitStats: true, see docs/adr-009-markdown-parsing.md)\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 --slim Compact node output for default/--query JSON: export names, meaningful\n tags, and a flat importsFiles list instead of full edge metadata\n (default: false — opt-in, unlike the MCP query tool which defaults slim to true)\n --root <dir> Project root directory (default: current directory)\n --init-skill Scaffold the mokosh Claude Code skill/command into .claude/\n --init-config Scaffold a starter mokosh.config.js in the project root\n --force Overwrite existing files (use with --init-skill/--init-config)\n --clear-cache Delete the cache file at --cache and exit\n --help Show help\n\nMCP parity (mirrors the MCP server's tools for use when MCP is unavailable):\n --dependencies List all files that --file imports (transitive; use with --depth)\n --dependents List files that directly import --file (one hop)\n --depth <N> Max traversal depth for --dependencies/--callers (default: 1)\n --affected List all files transitively affected if --file changes\n --tests-only Restrict --affected to test/spec files\n --changed-symbols <a,b,...> Restrict --affected to files that import these specific symbols\n --cached Use a precomputed impact cache for --affected instead of a fresh traversal\n --with-meta Add category/exports per result for --dependencies/--dependents/--affected\n --with-edge-detail Include from/to function names per edge (use with --callers)\n --find-complex-functions List functions/methods at or above a complexity threshold, worst-first\n --metric <name> cognitiveComplexity (default) | complexity — used by --find-complex-functions\n --complexity-threshold <N> Minimum score to include (default: 10, use with --find-complex-functions)\n --limit <N> Max results for --find-complex-functions/--find-duplicates\n --find-duplicates Find duplicated code blocks (token-based, works for every parsed\n language), largest-first. Lock files and files under an ignored\n directory (--config's ignoreDirs, merged with the defaults) are\n always excluded\n --min-duplicate-lines <N> Minimum duplicated block size in lines to report (default: 6,\n use with --find-duplicates)\n --workspace-packages List monorepo packages and their cross-package dependencies\n --workspace-affected Cross-package blast-radius for --file in a monorepo\n --find-symbol Find every file that exports --function <name> by exact name, with\n the best available caller/importer info per match (call-edge\n precision for TS/JS, named-import for Python, file-level otherwise)\n --watch Re-run on file changes. Supported with the default output, --query,\n --callers, --dependencies, --dependents, --affected, --find-uncovered,\n --find-complex-functions, --find-duplicates, --check-cycles,\n --check-doc-drift, and\n --list-tags only.\n\nNotes:\n Add mokosh-cache/ to your .gitignore to avoid committing the cache directory.\n --check-cycles is CLI-only (CI gate via exit code); it has no MCP equivalent by design.\n --check-doc-drift has an MCP equivalent (check_doc_drift) since its result list, not just a\n pass/fail exit code, is useful to an AI assistant deciding which docs to fix.\n --config files may be .js/.cjs and are executed for CLI convenience (allowJs: true); the MCP\n server only loads JSON config (allowJs: false) for security. This is intentional.\n --workspace-packages/--workspace-affected require explicit opt-in (unlike MCP's analyze tool,\n which auto-detects a monorepo when entryPoints is empty); other commands remain single-package.\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 | python | go | css | scss | less |\n stylus | 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\n minCoverage:<pct> Line coverage % >= N. No-data nodes are excluded.\n maxCoverage:<pct> Line coverage % <= N. No-data nodes are treated as 0%.\n Examples: minCoverage:80 maxCoverage:20\n\n minExportUsage:<0-1> Average exportUsageRatio >= N. No-data nodes are excluded.\n maxExportUsage:<0-1> Average exportUsageRatio <= N. No-data nodes treated as 0.\n Example: maxExportUsage:0.2\n\n minComplexity:<N> McCabe cyclomatic complexity >= N. No-data nodes excluded.\n maxComplexity:<N> McCabe cyclomatic complexity <= N. No-data nodes treated as 0.\n Example: minComplexity:15\n\n minCognitiveComplexity:<N> Cognitive complexity >= N. No-data nodes excluded.\n maxCognitiveComplexity:<N> Cognitive complexity <= N. No-data nodes treated as 0.\n Example: minCognitiveComplexity:10\n\n minCommits:<N> commitCount90d >= N (requires gitStats: true). No-data nodes excluded.\n maxCommits:<N> commitCount90d <= N. No-data nodes treated as 0.\n Example: minCommits:5\n\n isDocumented:<bool> true = at least one markdown doc references this file.\n false = undocumented-by-doc files only.\n Example: isDocumented:false\n\n isStale:<bool> true = node is flagged as doc-stale (staleFor non-empty; see\n check-doc-drift). Example: isStale:true\n\n lastAuthor:<value> Exact match on the file's most recent git author. Negate with !.\n Requires gitStats: true.\n Example: lastAuthor:jane\n\nOR LOGIC\n any(key:val|key:val) OR-group: node matches if it satisfies ANY single-key clause inside\n the group. ANDed with every other key in the query string. Each\n clause is a single key:value pair — multi-key AND clauses inside a\n group are not supported.\n Examples: any(category:logic|category:ui)\n path:src,any(tag:auth|tag:payments)\n\nSORTING & LIMITING (applied after all filters)\n sort:<field> Sort results 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 exportUsage — average exportUsageRatio\n complexity — McCabe cyclomatic complexity\n cognitiveComplexity — cognitive complexity\n Default direction is descending; see sortDir. Example: sort:imports\n\n sortDir:<asc|desc> Sort direction, applies to sort. Default: desc.\n Example: sort:size,sortDir:asc\n\n limit:<N> Return at most N results.\n Example: limit:20\n\nOUTPUT SHAPE\n --slim Pass alongside --query (or the default output) for a compact response:\n export names, meaningful tags, and a flat importsFiles path list — no\n edge objects, no mtime/size. Off by default on the CLI; the MCP query\n tool defaults this on.\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","/** Long-running `--watch` mode: re-runs a command whenever a source file changes, debounced. */\nimport fs from \"node:fs\";\nimport { IGNORE_WATCH } from \"../watch-ignore\";\n\n/**\n * @description Runs `rerun` immediately, then again on every debounced batch of file changes under\n * `rootDir` (ignoring `node_modules`/`.git`/`dist`/`build`/`coverage`, matching the MCP session\n * cache's watch behavior). Overlapping triggers collapse into a single trailing re-run. Never\n * returns on its own — the process stays alive until `SIGINT` closes the watcher and exits.\n * @param {string} rootDir - Absolute path of the directory to watch.\n * @param {number} debounceMs - Milliseconds to wait after the last change before re-running.\n * @param {() => Promise<void>} rerun - Rebuilds the graph and re-invokes the command handler.\n */\nexport function watchAndRun(rootDir: string, debounceMs: number, rerun: () => Promise<void>): void {\n let timer: NodeJS.Timeout | null = null;\n let running = false;\n let pending = false;\n\n const trigger = async () => {\n if (running) {\n pending = true;\n return;\n }\n running = true;\n try {\n await rerun();\n } finally {\n running = false;\n if (pending) {\n pending = false;\n void trigger();\n }\n }\n };\n\n void trigger();\n\n let watcher: fs.FSWatcher | null = null;\n try {\n watcher = fs.watch(rootDir, { recursive: true }, (_event, filename) => {\n if (!filename || IGNORE_WATCH.test(filename)) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void trigger();\n }, debounceMs);\n });\n } catch (err) {\n console.error(`Warning: unable to watch ${rootDir}: ${(err as Error).message}`);\n }\n\n process.on(\"SIGINT\", () => {\n if (timer) clearTimeout(timer);\n watcher?.close();\n process.exit(0);\n });\n}\n","/** Shared fs.watch ignore pattern, used by both the MCP session cache and the CLI's --watch mode. */\nexport const IGNORE_WATCH = /(?:^|[/\\\\])(?:node_modules|\\.git|dist|build|coverage)(?:[/\\\\]|$)/;\n","/** CLI entry point: parses arguments, loads configuration and the graph, then dispatches to the appropriate command. */\nimport {\n applyConfig,\n configToGraphOptions,\n createWorkspaceGraph,\n Graph,\n type MokoshConfig,\n} from \"../index\";\nimport type { ParsedArgs } from \"./args\";\nimport { parseArgs } from \"./args\";\nimport { run as runAffected } from \"./commands/affected\";\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 runCheckDocDrift } from \"./commands/check-doc-drift\";\nimport { runClearCache } from \"./commands/clear-cache\";\nimport { run as runDependencies } from \"./commands/dependencies\";\nimport { run as runDependents } from \"./commands/dependents\";\nimport { run as runDetectFeatures } from \"./commands/detect-features\";\nimport { run as runFeatureGraph } from \"./commands/feature-graph\";\nimport { run as runFindComplexFunctions } from \"./commands/find-complex-functions\";\nimport { run as runFindDuplicates } from \"./commands/find-duplicates\";\nimport { run as runFindSymbol } from \"./commands/find-symbol\";\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 { runInitConfig } from \"./commands/init-config\";\nimport { runInitSkill } from \"./commands/init-skill\";\nimport { run as runListTags } from \"./commands/list-tags\";\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 { CommandContext, CommandHandler } from \"./commands/types\";\nimport { runWorkspaceAffected } from \"./commands/workspace-affected\";\nimport { runWorkspacePackages } from \"./commands/workspace-packages\";\nimport { type ResolvedConfig, resolveConfig } from \"./config\";\nimport { buildGraph, loadGraphFromCache, saveGraphToCache } from \"./graph-loader\";\nimport { HELP_TEXT, QUERY_HELP_TEXT } from \"./help\";\nimport { watchAndRun } from \"./watch\";\n\n/** Commands that only read a snapshot and print — safe to re-run in a loop under `--watch`. */\nconst WATCHABLE_COMMANDS = new Set<CommandHandler>([\n runGraphOutput,\n runCallers,\n runDependencies,\n runDependents,\n runAffected,\n runFindUncovered,\n runFindComplexFunctions,\n runFindDuplicates,\n runCheckCycles,\n runCheckDocDrift,\n runListTags,\n]);\n\n/**\n * @description Handles the CLI's one-shot, no-graph-needed flags (help text, skill/config\n * scaffolding, cache clearing). Exits the process directly when one applies; does nothing\n * otherwise, letting `run()` continue to graph-building commands.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n */\nfunction handleEarlyExitFlags(parsed: ParsedArgs): void {\n if (parsed.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n }\n if (parsed.queryHelp) {\n console.log(QUERY_HELP_TEXT);\n process.exit(0);\n }\n if (parsed.initSkill) {\n runInitSkill(parsed.force);\n process.exit(0);\n }\n if (parsed.initConfig) {\n runInitConfig(parsed.force);\n process.exit(0);\n }\n if (parsed.clearCache) {\n runClearCache(parsed.cachePath);\n process.exit(0);\n }\n}\n\n/**\n * @description Handles `--workspace-packages`/`--workspace-affected`, which build a\n * `WorkspaceGraph` instead of the single-package `Graph` the rest of `run()` uses.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @param {ResolvedConfig} config - Resolved config, used for graph-build options.\n * @param {string} rootDir - Absolute project root.\n * @returns {Promise<boolean>} `true` if a workspace command was handled (caller should return immediately).\n */\nasync function runWorkspaceMode(\n parsed: ParsedArgs,\n config: ResolvedConfig,\n rootDir: string,\n): Promise<boolean> {\n if (!parsed.workspacePackages && !parsed.workspaceAffected) return false;\n\n if (parsed.watch) {\n console.error(\"Error: --watch is not supported with --workspace-packages/--workspace-affected\");\n process.exit(1);\n }\n\n const workspaceGraph = await createWorkspaceGraph(\n rootDir,\n configToGraphOptions(config.rawConfig),\n );\n\n if (parsed.workspacePackages) {\n runWorkspacePackages(workspaceGraph);\n } else {\n if (!parsed.file) {\n console.error(\"Error: --workspace-affected requires --file <path>\");\n process.exit(1);\n }\n runWorkspaceAffected(workspaceGraph, parsed.file);\n }\n return true;\n}\n\n/**\n * @description Whether any flag requires building/loading a graph without explicit entry\n * points (e.g. `--call-graph`, `--callers`), reusing whatever's cached from a prior run.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @returns {boolean} `true` if entry points may be omitted because a command flag implies scanning.\n */\nfunction computeAutoScan(parsed: ParsedArgs): boolean {\n return (\n parsed.proposeTags ||\n parsed.affectedTests ||\n parsed.applyTags ||\n parsed.callers ||\n parsed.dependencies ||\n parsed.dependents ||\n parsed.affected ||\n parsed.findUncovered ||\n parsed.listTags ||\n parsed.findComplexFunctions ||\n parsed.findDuplicates ||\n parsed.typeGraph ||\n parsed.moduleResponsibility ||\n parsed.callGraph ||\n parsed.findSymbol ||\n parsed.apiSurface\n );\n}\n\ninterface CommandEntry {\n name: string;\n flag: boolean;\n handler: CommandHandler;\n}\n\n/**\n * @description Resolves which command flag was passed to a handler. Errors out if more than\n * one command flag is set at once, rather than silently picking whichever comes first.\n * @param {ParsedArgs} parsed - The fully parsed CLI arguments.\n * @returns {CommandHandler} The handler for the single active command flag, or the default\n * graph-output handler if none are set.\n */\nexport function resolveCommandHandler(parsed: ParsedArgs): CommandHandler {\n const commands: CommandEntry[] = [\n { name: \"--propose-tags\", flag: parsed.proposeTags, handler: runProposeTags },\n { name: \"--apply-tags\", flag: parsed.applyTags, handler: runApplyTags },\n { name: \"--affected-tests\", flag: parsed.affectedTests, handler: runAffectedTests },\n { name: \"--detect-features\", flag: parsed.detectFeatures, handler: runDetectFeatures },\n { name: \"--find-unused\", flag: parsed.findUnused, handler: runFindUnused },\n { name: \"--check-cycles\", flag: parsed.checkCycles, handler: runCheckCycles },\n { name: \"--check-doc-drift\", flag: parsed.checkDocDrift, handler: runCheckDocDrift },\n { name: \"--find-uncovered\", flag: parsed.findUncovered, handler: runFindUncovered },\n { name: \"--list-tags\", flag: parsed.listTags, handler: runListTags },\n { name: \"--callers\", flag: parsed.callers, handler: runCallers },\n { name: \"--dependencies\", flag: parsed.dependencies, handler: runDependencies },\n { name: \"--dependents\", flag: parsed.dependents, handler: runDependents },\n { name: \"--affected\", flag: parsed.affected, handler: runAffected },\n {\n name: \"--find-complex-functions\",\n flag: parsed.findComplexFunctions,\n handler: runFindComplexFunctions,\n },\n { name: \"--find-duplicates\", flag: parsed.findDuplicates, handler: runFindDuplicates },\n { name: \"--type-graph\", flag: parsed.typeGraph, handler: runTypeGraph },\n {\n name: \"--module-responsibility\",\n flag: parsed.moduleResponsibility,\n handler: runModuleResponsibility,\n },\n { name: \"--feature-graph\", flag: parsed.featureGraph, handler: runFeatureGraph },\n { name: \"--call-graph\", flag: parsed.callGraph, handler: runCallGraph },\n { name: \"--find-symbol\", flag: parsed.findSymbol, handler: runFindSymbol },\n { name: \"--api-surface\", flag: parsed.apiSurface, handler: runApiSurface },\n ];\n\n const active = commands.filter((command) => command.flag);\n if (active.length > 1) {\n console.error(\n `Error: conflicting flags: ${active.map((command) => command.name).join(\", \")} (pass only one command flag)`,\n );\n process.exit(1);\n }\n\n return active[0]?.handler ?? runGraphOutput;\n}\n\n/**\n * @description Runs `handler` once immediately, then re-runs it on every debounced file-change\n * batch, rebuilding and re-caching the graph each time.\n * @param {CommandHandler} handler - The resolved command handler to re-invoke.\n * @param {CommandContext} ctx - Base command context; only `graph` is replaced on each re-run.\n * @param {string} rootDir - Absolute project root, watched for changes.\n * @param {string[]} resolvedEntryPoints - Entry points to rebuild the graph from.\n * @param {string} resolvedCachePath - Where to persist the rebuilt graph.\n * @param {boolean} silent - Suppresses build progress output when true.\n * @param {MokoshConfig} rawConfig - Config used to derive graph-build options on rebuild.\n */\nasync function runWatchLoop(\n handler: CommandHandler,\n ctx: CommandContext,\n rootDir: string,\n resolvedEntryPoints: string[],\n resolvedCachePath: string,\n silent: boolean,\n rawConfig: MokoshConfig,\n): Promise<void> {\n if (!WATCHABLE_COMMANDS.has(handler)) {\n console.error(\n \"Error: --watch is only supported with the default output, --query, --callers, --dependencies, --dependents, --affected, --find-uncovered, --find-complex-functions, --find-duplicates, --check-cycles, --check-doc-drift, and --list-tags\",\n );\n process.exit(1);\n }\n watchAndRun(rootDir, 300, async () => {\n const freshGraph = await buildGraph(rootDir, resolvedEntryPoints, ctx.graph, {\n silent,\n ...configToGraphOptions(rawConfig),\n });\n saveGraphToCache(freshGraph, resolvedCachePath);\n console.log(`--- rebuilt at ${new Date().toISOString()} ---`);\n await handler({ ...ctx, graph: freshGraph });\n });\n}\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 handleEarlyExitFlags(parsed);\n\n const config = resolveConfig(parsed);\n applyConfig(config.rawConfig);\n const { rootDir, resolvedEntryPoints, resolvedCachePath, scanOptions } = config;\n\n if (await runWorkspaceMode(parsed, config, rootDir)) return;\n\n const autoScan = computeAutoScan(parsed);\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 !parsed.findUnused &&\n !parsed.detectFeatures &&\n !parsed.checkCycles &&\n !parsed.checkDocDrift\n ) {\n console.error(\"Error: No entry points provided\");\n process.exit(1);\n }\n graph = await buildGraph(rootDir, resolvedEntryPoints, graph, {\n silent: parsed.silent,\n ...configToGraphOptions(config.rawConfig),\n });\n\n saveGraphToCache(graph, resolvedCachePath);\n }\n\n const ctx: CommandContext = {\n graph,\n rootDir,\n entryPoints: resolvedEntryPoints.map((entryPath) => entryPath.replace(`${rootDir}/`, \"\")),\n scanOptions,\n rawConfig: config.rawConfig,\n featureThreshold: parsed.featureThreshold,\n queryStr: parsed.query,\n mermaidOutput: parsed.mermaid,\n plain: parsed.plain,\n excludeTests: parsed.excludeTests,\n file: parsed.file,\n typeFilter: parsed.typeFilter,\n filterPaths: parsed.filterPaths,\n minOutDegree: parsed.minOutDegree,\n functionName: parsed.functionName,\n dryRun: parsed.dryRun,\n depth: parsed.depth,\n cached: parsed.cached,\n changedSymbols: parsed.changedSymbols,\n withMeta: parsed.withMeta,\n withEdgeDetail: parsed.withEdgeDetail,\n metric: parsed.metric,\n complexityThreshold: parsed.complexityThreshold,\n limit: parsed.limit,\n slim: parsed.slim,\n testsOnly: parsed.testsOnly,\n minDuplicateLines: parsed.minDuplicateLines,\n };\n\n const handler = resolveCommandHandler(parsed);\n\n if (parsed.watch) {\n await runWatchLoop(\n handler,\n ctx,\n rootDir,\n resolvedEntryPoints,\n resolvedCachePath,\n parsed.silent,\n config.rawConfig,\n );\n return;\n }\n\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","configToGraphOptions","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","computeGraphHash","graph","entries","pathA","pathB","filePath","node","hash","i","buildChangeImpactCache","impact","affected","queryChangeImpact","cache","import_promises","import_node_path","import_piscina","import_node_fs","import_node_path","import_js_yaml","LOCK_FILE_NAMES","stripVersionSuffix","descriptor","lastAt","parseYarnDescriptors","line","part","trimmed","parsePnpmId","id","pkgVersion","raw","tryParseYarnBerry","content","lock","loadYaml","result","key","value","name","parseYarnClassic","block","lines","header","names","version","parsePackageLock","filePath","fs","pkgPath","pkgData","parseYarnLock","berryResult","parsePnpmLock","versionData","loadLockFile","rootDir","parsers","candidates","filename","parser","path","STYLE_FILE_TYPES","getDuplicateFamily","fileType","hashWindow","text","hash","i","hashWindows","tokens","windowSize","hashes","t","extendChain","hashesByFile","a","b","hashesA","hashesB","extra","isChainStart","ExactOccurrenceUnionFind","key","root","cur","next","keyA","keyB","rootA","rootB","occurrenceKey","occ","STRUCTURAL_PUNCTUATION","structuralPunctuationRatio","start","length","count","DEFAULT_MAX_BUCKET_SIZE","findDuplicateGroups","files","minLines","maxPunctuationRatio","maxBucketSize","tokensByFile","buckets","file","loc","bucket","pairMatches","skippedBuckets","locations","j","tokensA","lo","hi","tokensB","occA","occB","lines","clusters","occurrenceByKey","keysByRoot","keys","groups","occurrences","pm","import_postcss","less","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","extractLessVariableExports","exports","tags","name","parseLessContent","import_postcss_scss","isScssPrivateName","name","isScssExternal","specifier","parseScssParams","params","specMatch","alias","extractScssExports","root","exports","tags","node","signature","parseScssContent","content","filePath","scssParse","imports","edge","normalizeValue","value","rootFor","file","parseScssContent","parseLessContent","parseCssContent","collectRuleBlocks","root","blocks","rule","decls","node","firstDecl","lastDecl","startLine","endLine","signature","decl","findStyleBlockDuplicates","files","minLines","minDeclarations","buckets","block","bucket","groups","j","a","b","occA","occB","COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","fileType","ignoreLiterals","stripped","tokens","line","lastIndex","match","i","raw","text","DEFAULT_MIN_FILES_FOR_POOL","STRUCTURAL_STYLE_TYPES","isLockFile","relPath","LOCK_FILE_NAMES","path","isUnderIgnoredDir","ignoreDirs","ignored","segment","findDuplicates","graph","rootDir","options","minLines","windowSize","ignoreLiterals","maxPunctuationRatio","maxBucketSize","limit","DEFAULT_IGNORE_DIRS","parallelTokenizing","nodes","node","pool","createTokenizingPool","structuralStyleFiles","filesByFamily","source","family","getDuplicateFamily","tokens","tokenize","fileTokens","bucket","groups","findStyleBlockDuplicates","skippedBuckets","result","findDuplicateGroups","group","a","b","option","candidateFileCount","opts","minFiles","Piscina","err","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","EXPORT_TRACKING_TYPES","IMPORT_SYMBOL_TYPES","CALL_EDGE_TYPES","getLanguageCoverage","graph","counts","node","type","fileCount","a","b","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","SymbolTraversalContext","startPath","affectedSymbols","visitedNode","childPath","currentSymbols","importEdge","imp","importedSymbols","relevantSymbols","sym","existing","symbol","getNodeMeta","graph","path","node","exportedSym","getDependencies","file","depth","deps","_depth","parentPath","edge","importEdge","getDependents","dependents","getAffected","options","testsOnly","changedSymbols","ctx","SymbolTraversalContext","affected","isTest","tag","getCallers","withEdgeDetail","callers","entry","callEdge","findComplexFunctions","metric","threshold","limit","fn","a","b","slimSerialize","filtered","imp","summarizeWorkspacePackages","wg","pkgDeps","packages","pkg","hasCoverageData","inferRole","node","filePath","seg","fileBasename","segment","name","buildResponsibilityGraph","graph","featureOptions","featureGraph","buildFeatureGraph","fileToHub","featureName","domain","filePath","result","node","hub","inferRole","exportedSym","findSymbol","graph","name","matches","node","exportedSym","CALL_EDGE_TYPES","callers","other","edge","importers","getDependents","dependent","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","loadYaml","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","getMonorepoDetectors","registerMonorepoDetector","turborepoDetector","nxDetector","pnpmDetector","yarnDetector","npmDetector","detectMonorepo","rootDir","detectors","getMonorepoDetectors","abs","path","allPackages","detectedTypes","detector","pkgs","pkg","packages","WorkspaceGraph","_WorkspaceGraph","monorepoRoot","type","pkg","graph","relPath","deps","pkgDeps","node","imp","ownerPkg","ownerEntry","result","data","wg","nodes","nodeMap","Graph","parserRegistry","registerParser","type","parser","getParserForType","matchesStr","nodeValue","queryValue","matchesPath","nodePath","queryPath","matchCategory","node","query","matchType","matchLastAuthor","matchPath","matchIsExternal","importEdge","matchTags","positiveTags","tag","negativeTags","structuredTag","matchAllTags","matchImportsFile","matchImportedBy","reverseIndex","importerPath","matchMinImports","matchMaxImports","matchMinSize","matchMaxSize","matchHasDocstring","matchIsDocumented","matchIsStale","matchMinCoverage","matchMaxCoverage","matchMinExportUsage","matchMaxExportUsage","matchMinComplexity","matchMaxComplexity","matchMinCognitiveComplexity","matchMaxCognitiveComplexity","matchMinCommits","matchMaxCommits","matchAny","subQuery","matchNode","NODE_MATCHERS","matcher","filterGraph","graph","query","reverseIndex","node","imp","arr","filteredNodes","matchNode","nodePaths","resultNodes","direction","nodeA","nodeB","diff","cycle","path","applyClause","query","part","colonIdx","key","value","parseAnyGroup","inner","subQueries","clause","trimmedClause","subQuery","parseQuery","queryString","parts","trimmedPart","import_promises","import_node_path","import_node_path","import_typescript","import_node_path","import_node_path","import_typescript","ANNOTATABLE_NAMES","at","arr","index","value","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","stripAtPrefix","raw","applyAtPrefixedTagProp","absPath","path","firstCall","rawExisting","sortedTags","literal","CypressStrategy","absPath","TS_EXTENSIONS","path","source","tags","applyAtPrefixedTagProp","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","PlaywrightStrategy","absPath","TS_EXTENSIONS","path","source","tags","applyAtPrefixedTagProp","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","sum","import_node_path","import_typescript","LEGACY_BLOCK_REGEX","VitestStrategy","absPath","TS_EXTENSIONS","path","source","tags","stripped","sf","ts","calls","findTopLevelCalls","firstCall","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_piscina","import_node_child_process","DefaultGitProvider","allFiles","args","filePath","error","COMMIT_HEADER_FORMAT","MAX_LOG_BUFFER_BYTES","walkNameStatusLog","output","onFile","authorEmail","timestampMs","line","email","at","parts","status","getRepoGitStats","rootDir","stats","recentOutput","existing","fullOutput","_authorEmail","import_node_path","getFileType","filePath","path","isStyleFile","specifier","ext","import_coffeescript","stripQuotes","value","extractTagAnnotations","content","tags","tagRegex","match","classifyTestOrLogic","filePath","lower","stringLiteralValue","node","stripQuotes","identifierName","edgeFromImportDeclaration","filePath","specifier","isStyleFile","edgeFromRequireCall","isRequire","exportsFromAssignment","assignNode","propertyName","rhs","className","property","name","bareName","exportsFromNamedClause","clause","visitNode","imports","exports","edge","base","traverse","key","child","c","parseCoffeeScript","content","tags","extractTagAnnotations","category","classifyTestOrLogic","coffee","seenNames","dedupedExports","symbol","import_gherkin","import_messages","uuidFn","parseGherkin","_filePath","content","rawTags","builder","matcher","gherkinDocument","tag","child","example","ruleChild","error","name","registerParser","import_node_path","import_go","childrenOf","node","result","child","lineAt","content","pos","line","i","computeCyclomaticComplexity","rootNode","content","complexity","walk","node","text","child","computeCognitiveComplexity","cognitive","depth","isElseIf","name","kids","childrenOf","bodyDepth","cond","thenBlock","k","elseIndex","after","computeComplexity","receiverTypeName","methodDecl","receiverParam","typeNode","collectFunctionComplexity","tree","results","cursor","nameNode","cognitiveComplexity","lineAt","methodName","receiver","TAG_RE","BUILD_NEW_RE","BUILD_OLD_RE","parseGo","filePath","content","imports","exportMap","tags","buildTags","packageIdents","tree","cursor","text","tagM","newBuild","extractBuildTokens","oldBuild","stringNode","specifier","aliasNode","ident","nameNode","name","importsTestingPkg","importEdge","category","path","allTagNames","complexity","cognitiveComplexity","computeComplexity","functions","collectFunctionComplexity","rawCallEdges","collectRawCallEdges","edges","walkBody","node","callerName","callee","pkgIdentNode","fieldNode","pkgIdent","toSpecifier","to","child","body","methodName","receiver","receiverTypeName","expr","out","tok","import_livescript","identifierName","node","exportsFromValue","rhs","className","prop","name","bareName","extractExports","type","chain","base","propertyName","POSITIONAL_KEYS","extractEdge","filePath","raw","specifier","stripQuotes","isStyleFile","call","collectEdges","exports","edges","edge","key","child","c","parseLiveScript","content","tags","extractTagAnnotations","category","classifyTestOrLogic","imports","ls","seenNames","dedupedExports","symbol","import_luaparse","collectRequireEdges","ast","filePath","importEdges","visitNode","node","specifier","requireArgument","stripQuotes","isStyleFile","key","childValue","childNode","collectModuleTableNames","topLevelStatements","moduleTableNames","statement","variable","index","collectReturnTableExports","lastStatement","returnedTable","exportedSymbols","field","collectExports","seenNames","symbol","parseLua","content","tagNames","extractTagAnnotations","category","classifyTestOrLogic","imports","exports","luaparse","name","EXTERNAL_LINK_PREFIXES","CODE_EXTENSIONS","PATH_TOKEN_PATTERN","processorPromise","getProcessor","unified","remarkParse","isExternalLink","url","trimmed","p","edgesFromCodeText","text","filePath","matches","specifier","walk","node","edges","child","parseMarkdown","content","tree","seen","edge","import_node_path","import_python","computeCyclomaticComplexity","rootNode","complexity","walk","node","childrenOf","k","child","computeCognitiveComplexity","cognitive","depth","name","kids","branchIndex","i","kw","isElseIf","bodyDepth","cond","body","computeComplexity","collectFunctionComplexity","tree","content","results","recordFunction","cognitiveComplexity","lineAt","walkChildren","classNameNode","className","fnNameNode","fnName","nameNode","TEST_LIBS","parsePython","filePath","content","imports","exports","tags","baseName","path","tree","cursor","tagMatch","edge","extractImportEdges","parentNode","nameNode","target","category","resolveCategory","complexity","cognitiveComplexity","computeComplexity","functions","collectFunctionComplexity","rawCallEdges","collectRawCallEdges","name","node","src","first","extractFromImport","extractBareImport","fromKw","importKw","rawModule","importedNames","collectImportedNames","dotCount","modulePart","makeEdge","prefix","edges","childNode","modName","start","names","buildImportSymbolMap","symbolMap","child","importedName","localName","aliasNode","specifier","importSymbols","walkBody","callerName","calleeNode","calleeName","toSpecifier","walkChildren","walk","classNameNode","className","body","fnNameNode","fnName","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","parseStylusImports","content","filePath","imports","atRequirePattern","match","specifier","bareImportPattern","detectStylusCategory","stylusLib","astNode","parseStyleFile","filePath","content","fileType","getFileType","imports","parseStylusImports","detectStylusCategory","root","exports","tags","parseScssContent","detectCssBarrel","parseLessContent","parseCssContent","type","parser","path","content","parseCodeFile","parseStyleFile","parseCoffeeScript","parseLiveScript","parseLua","parsePython","parseGo","parseGherkin","parseMarkdown","registerParser","parseFile","filePath","fileType","getFileType","getParserForType","import_node_path","enrichCoverage","nodes","coverageMap","node","pct","enrichLibraryTags","imports","tags","imp","libName","existingTag","enrichTestedBy","target","enrichDocDrift","round4","value","enrichExportUsage","ratios","ratio","sum","addUniqueTag","name","kind","addFilenameTag","testNode","importEdge","toPath","filenameTag","path","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_path","MarkdownLangResolver","_currentFile","specifier","rootDir","resolveLocal","resolved","path","import_node_fs","import_node_path","PythonLangResolver","_currentFile","specifier","rootDir","_resolveLocal","pyPath","path","pyFile","isFile","initFile","filePath","fs","import_node_path","EXTERNAL_PREFIXES","StyleLangResolver","currentFile","specifier","_rootDir","resolveLocal","prefix","resolved","dir","path","base","partialSpecifier","partialResolved","DefaultResolver","rootDir","options","PythonLangResolver","LuaLangResolver","GoLangResolver","StyleLangResolver","MarkdownLangResolver","currentFile","specifier","aliased","resolved","resolveLocal","cf","spec","lr","ext","locals","workspace","dir","path","fullPath","isExternal","extensions","esmMatch","strippedPath","candidatePath","indexP","initP","filePath","fs","alias","match","substitutions","searchDir","tsconfigPath","paths","regex","pattern","wildcardMatch","baseDir","sub","resolvedSub","pkgName","pkgRoot","subPath","base","candidate","abs","deepResolved","CONVENTIONAL_TEST_DIR_NAMES","WALK_IGNORE_DIRS","DEFAULT_MIN_FILES_FOR_POOL","commonAncestorDir","absPaths","rootDir","segmentLists","p","path","common","segments","i","candidate","rel","GraphBuilder","previousGraph","resolver","progressCallback","enableGitStats","coverageMap","parallelParsing","DefaultResolver","loadLockFile","entryPoints","entryPaths","entry","getRepoGitStats","entryPath","enrichTestNodeTags","enrichTestedBy","enrichExportUsage","enrichDocDrift","enrichCoverage","Graph","opts","minFiles","workerFilename","Piscina","err","dir","threshold","count","stack","current","entries","fs","scanRoot","patterns","getTestPatterns","matchesTest","pattern","parent","name","matches","fullPath","filePath","batch","stats","relativePath","node","enrichLibraryTags","cachedNode","parsed","callEdges","content","parseFile","getFileType","rawCallEdges","rce","resolved","imports","exports","tags","category","description","complexity","cognitiveComplexity","functions","git","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","abs","path","GraphBuilder","DefaultResolver","createWorkspaceGraph","layout","detectMonorepo","pkgs","pkg","workspaceMap","wg","WorkspaceGraph","graph","getAllProjectFiles","files","ignoreDirs","DEFAULT_IGNORE_DIRS","extensions","DEFAULT_EXTENSIONS","walk","dir","entries","fs","entry","fullPath","import_node_path","import_node_util","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","resolveRootDir","cliTokens","i","path","parseOptionalInt","raw","parseCsv","part","OPTIONS","STRING_FLAGS","v","k","sanitizeTokens","result","token","valueToken","parseArgs","rootDir","defaultCachePath","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","values","positionals","nodeParseArgs","cacheValue","configValue","metricRaw","run","ctx","graph","file","testsOnly","changedSymbols","cached","withMeta","annotate","paths","p","getNodeMeta","impactCache","buildChangeImpactCache","allAffected","queryChangeImpact","affected","filePath","result","getAffected","import_node_path","TEST_PATTERNS","getTestFiles","allFiles","filePath","base","path","pattern","resolveChangedFiles","rootDir","DefaultGitProvider","run","ctx","graph","rootDir","scanOptions","featureThreshold","rawConfig","changedFiles","resolveChangedFiles","node","getTestFiles","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","affectedTests","proposeAffectedTests","run","ctx","graph","rootDir","entryPoints","eps","detectAllEntryPoints","surface","buildApiSurface","run","ctx","graph","rootDir","scanOptions","dryRun","plain","rawConfig","allFiles","getAllProjectFiles","createImportMap","getTestFiles","configToGraphOptions","result","applyTags","run","ctx","graph","functionName","result","queryCallGraph","run","ctx","graph","file","plain","depth","withEdgeDetail","callers","getCallers","caller","run","ctx","graph","cycles","cycle","run","ctx","graph","staleDocs","node","doc","staleFor","import_node_fs","runClearCache","cachePath","fs","run","ctx","graph","file","depth","withMeta","deps","getDependencies","dependencies","dep","getNodeMeta","run","ctx","graph","file","withMeta","deps","getDependents","dependents","dep","getNodeMeta","run","ctx","graph","rootDir","scanOptions","featureThreshold","rawConfig","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","featureMap","detectFeatures","features","featureA","featureB","run","ctx","graph","rootDir","scanOptions","minOutDegree","rawConfig","allFiles","getAllProjectFiles","createImportMap","configToGraphOptions","featureGraph","buildFeatureGraph","features","run","ctx","graph","metric","complexityThreshold","limit","threshold","functions","findComplexFunctions","run","ctx","graph","rootDir","scanOptions","minDuplicateLines","limit","minLines","ignoreDirs","DEFAULT_IGNORE_DIRS","groups","skippedBuckets","findDuplicates","run","ctx","graph","functionName","plain","matches","findSymbol","match","run","ctx","graph","featureThreshold","rawConfig","plain","threshold","hasCoverageData","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","slim","serialized","query","parseQuery","filterGraph","filteredGraph","Graph","MermaidExporter","cycles","languageCoverage","getLanguageCoverage","slimSerialize","import_node_fs","import_node_path","import_node_fs","import_node_path","findTemplateDir","startDir","subpathSegments","markerFile","dir","i","candidate","path","fs","parent","runInitConfig","force","cwd","templateDir","findTemplateDir","src","path","dest","fs","import_node_fs","import_node_path","TARGETS","runInitSkill","force","cwd","templateDir","findTemplateDir","target","src","path","dest","relDest","fs","run","ctx","graph","plain","counts","node","tag","tags","name","count","a","b","run","ctx","graph","filterPaths","minOutDegree","respGraph","buildResponsibilityGraph","modules","modulePath","run","ctx","graph","rootDir","scanOptions","featureThreshold","plain","rawConfig","changedFiles","resolveChangedFiles","allFiles","getAllProjectFiles","createImportMap","getTestFiles","configToGraphOptions","tags","proposeTags","run","ctx","graph","typeFilter","typeGraph","buildTypeGraph","result","queryTypeGraph","types","runWorkspaceAffected","wg","file","affected","runWorkspacePackages","wg","summarizeWorkspacePackages","import_node_path","resolveCachePath","cachePath","defaultCachePath","configCachePath","rootDir","path","resolveConfig","parsed","entryPoints","configPath","config","loadMokoshConfig","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","resolvedEntryPoints","resolvedCachePath","scanOptions","import_node_fs","import_node_path","loadGraphFromCache","cachePath","fs","raw","Graph","saveGraphToCache","graph","cacheDir","path","buildGraph","rootDir","entryPoints","cachedGraph","options","configToGraphOptions","createImportMap","HELP_TEXT","QUERY_HELP_TEXT","import_node_fs","IGNORE_WATCH","watchAndRun","rootDir","debounceMs","rerun","timer","running","pending","trigger","watcher","fs","_event","filename","IGNORE_WATCH","err","WATCHABLE_COMMANDS","run","handleEarlyExitFlags","parsed","HELP_TEXT","QUERY_HELP_TEXT","runInitSkill","runInitConfig","runClearCache","runWorkspaceMode","config","rootDir","workspaceGraph","createWorkspaceGraph","configToGraphOptions","runWorkspacePackages","runWorkspaceAffected","computeAutoScan","resolveCommandHandler","active","command","runWatchLoop","handler","ctx","resolvedEntryPoints","resolvedCachePath","silent","rawConfig","watchAndRun","freshGraph","buildGraph","saveGraphToCache","argv","parseArgs","resolveConfig","applyConfig","scanOptions","autoScan","graph","loadGraphFromCache","Graph","entryPath","run","err"]}
|