@omfalos/mokosh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +139 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +139 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +1135 -0
- package/dist/index.d.ts +1135 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +26 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcp.d.mts +15 -0
- package/dist/mcp.d.ts +15 -0
- package/dist/mcp.js +28 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mcp.mjs +28 -0
- package/dist/mcp.mjs.map +1 -0
- package/package.json +103 -0
package/dist/mcp.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/mcp/server.ts","../src/mcp/cache.ts","../src/config.ts","../src/parser/classify.ts","../src/const.ts","../src/coverage.ts","../src/exporters/mermaid.ts","../src/graph/api-surface.ts","../src/graph/call-graph/index.ts","../src/graph/change-impact-cache.ts","../src/graph/features/index.ts","../src/graph/features/feature-graph.ts","../src/graph/analyzer.ts","../src/graph/model.ts","../src/graph/responsibility/infer-role.ts","../src/graph/responsibility/index.ts","../src/graph/symbol-traversal.ts","../src/graph/type-graph.ts","../src/graph/workspace/index.ts","../src/graph/workspace/detectors/npm.ts","../src/graph/workspace/shared.ts","../src/graph/workspace/fs-utils.ts","../src/graph/workspace/detectors/nx.ts","../src/graph/workspace/detectors/pnpm.ts","../src/graph/workspace/detectors/turborepo.ts","../src/graph/workspace/detectors/yarn.ts","../src/graph/workspace/registry.ts","../src/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/lockfile.ts","../src/parser/file-type.ts","../src/parser/lang/coffee.ts","../src/parser/lang/gherkin.ts","../src/parser/lang/go.ts","../src/parser/lang/ls.ts","../src/parser/utils.ts","../src/parser/lang/lua.ts","../src/parser/lang/python.ts","../src/parser/lang/typescript.ts","../src/parser/complexity.ts","../src/parser/tagging/index.ts","../src/parser/style/barrel.ts","../src/parser/style/css.ts","../src/parser/style/scss.ts","../src/parser/style/stylus.ts","../src/parser/style/index.ts","../src/parser.ts","../src/graph/enrichment.ts","../src/graph/resolver.ts","../src/graph/lang-resolvers/go.ts","../src/graph/lang-resolvers/lua.ts","../src/graph/lang-resolvers/python.ts","../src/tags/proposer.ts","../src/index.ts","../src/mcp/handlers.ts","../src/mcp/utils.ts","../src/mcp/tools.ts"],"sourcesContent":["#!/usr/bin/env node\n/** MCP server entry point: bootstraps the server and connects it to the stdio transport. */\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createMcpServer } from \"./mcp/server\";\n\nexport { createMcpServer } from \"./mcp/server\";\n\n/**\n * @description Bootstraps the MCP server by creating an instance and connecting\n * it to the stdio transport, making all registered tools available to MCP-compatible clients.\n */\nasync function main() {\n const server = createMcpServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch(console.error);\n","/** Creates and configures the MCP server, wiring all tool handlers to their JSON Schema definitions. */\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { SessionState } from \"./cache\";\nimport {\n type AnalyzeArgs,\n type ApplyTagsArgs,\n type ClearCacheArgs,\n type DetectFeaturesArgs,\n type FindComplexFunctionsArgs,\n type FindUncoveredArgs,\n type FindUnusedArgs,\n type GetAffectedArgs,\n type GetApiSurfaceArgs,\n type GetCallersArgs,\n type GetCallGraphArgs,\n type GetDependenciesArgs,\n type GetDependentsArgs,\n type GetFeatureGraphArgs,\n type GetModuleResponsibilityArgs,\n type GetTypeGraphArgs,\n type GetWorkspaceAffectedArgs,\n type GetWorkspacePackagesArgs,\n handleAnalyze,\n handleApplyTags,\n handleClearCache,\n handleDetectFeatures,\n handleFindComplexFunctions,\n handleFindUncovered,\n handleFindUnused,\n handleGetAffected,\n handleGetApiSurface,\n handleGetCallers,\n handleGetCallGraph,\n handleGetDependencies,\n handleGetDependents,\n handleGetFeatureGraph,\n handleGetModuleResponsibility,\n handleGetTypeGraph,\n handleGetWorkspaceAffected,\n handleGetWorkspacePackages,\n handleProposeTags,\n handleQuery,\n type ProposeTagsArgs,\n type QueryArgs,\n type ToolArgs,\n} from \"./handlers\";\nimport { TOOL_DEFINITIONS } from \"./tools\";\nimport { type TextResponse, validateRoot } from \"./utils\";\n\n/**\n * @description Creates and wires up the mokosh MCP server. Each call returns a fresh `Server`\n * instance backed by its own `SessionState`, so multiple instances (e.g. parallel test runs)\n * are fully isolated. Every incoming `root` argument is validated to be within the user's home\n * directory before any handler runs.\n * @returns {Server} A configured MCP `Server` ready to be connected to a transport.\n */\nexport function createMcpServer(): Server {\n const cache = new SessionState();\n\n const server = new Server({ name: \"mokosh\", version: \"0.0.1\" }, { capabilities: { tools: {} } });\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOL_DEFINITIONS,\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: rawArgs } = request.params;\n\n const toolArgs = rawArgs as unknown as ToolArgs;\n\n const dispatch: Record<string, (args: ToolArgs) => Promise<TextResponse> | TextResponse> = {\n analyze: (args) => handleAnalyze(cache, args as AnalyzeArgs),\n get_dependencies: (args) => handleGetDependencies(cache, args as GetDependenciesArgs),\n get_dependents: (args) => handleGetDependents(cache, args as GetDependentsArgs),\n get_affected: (args) => handleGetAffected(cache, args as GetAffectedArgs),\n get_callers: (args) => handleGetCallers(cache, args as GetCallersArgs),\n find_unused: (args) => handleFindUnused(cache, args as FindUnusedArgs),\n find_uncovered: (args) => handleFindUncovered(cache, args as FindUncoveredArgs),\n find_complex_functions: (args) =>\n handleFindComplexFunctions(cache, args as FindComplexFunctionsArgs),\n propose_tags: (args) => handleProposeTags(cache, args as ProposeTagsArgs),\n detect_features: (args) => handleDetectFeatures(cache, args as DetectFeaturesArgs),\n get_type_graph: (args) => handleGetTypeGraph(cache, args as GetTypeGraphArgs),\n get_module_responsibility: (args) =>\n handleGetModuleResponsibility(cache, args as GetModuleResponsibilityArgs),\n get_feature_graph: (args) => handleGetFeatureGraph(cache, args as GetFeatureGraphArgs),\n get_call_graph: (args) => handleGetCallGraph(cache, args as GetCallGraphArgs),\n get_api_surface: (args) => handleGetApiSurface(cache, args as GetApiSurfaceArgs),\n query: (args) => handleQuery(cache, args as QueryArgs),\n get_workspace_packages: (args) =>\n handleGetWorkspacePackages(cache, args as GetWorkspacePackagesArgs),\n get_workspace_affected: (args) =>\n handleGetWorkspaceAffected(cache, args as GetWorkspaceAffectedArgs),\n clear_cache: (args) => handleClearCache(cache, args as ClearCacheArgs),\n apply_tags: (args) => handleApplyTags(cache, args as ApplyTagsArgs),\n };\n\n const handler = dispatch[name];\n if (!handler) throw new Error(`Unknown tool: ${name}`);\n\n try {\n validateRoot(toolArgs.root);\n return await handler(toolArgs);\n } catch (err) {\n return {\n content: [\n { type: \"text\", text: `Error: ${err instanceof Error ? err.message : String(err)}` },\n ],\n isError: true,\n };\n }\n });\n\n return server;\n}\n","/** Session-scoped graph cache keyed by root directory, shared across MCP tool calls in one session. */\nimport fs, { type FSWatcher } from \"node:fs\";\nimport type { MokoshConfig } from \"../config\";\nimport {\n buildChangeImpactCache,\n type ChangeImpactCache,\n createImportMap,\n createWorkspaceGraph,\n type Graph,\n type WorkspaceGraph,\n} from \"../index\";\n\ntype LastAnalyzeArgs =\n | { kind: \"single\"; entryPoints: string[]; coverageMap: Map<string, number> }\n | { kind: \"workspace\" };\n\nconst IGNORE_WATCH = /(?:^|[/\\\\])(?:node_modules|\\.git|dist|build|coverage)(?:[/\\\\]|$)/;\n\n/**\n * Per-session state keyed by absolute project root path.\n *\n * Holds both the parsed dependency graphs and config-initialisation bookkeeping\n * so all tool calls within one MCP session can share the same in-memory state\n * without re-parsing or re-applying config on every request.\n *\n * Each `createMcpServer()` call creates its own `SessionState`, keeping\n * parallel server instances (e.g. in tests) fully isolated.\n */\nexport class SessionState {\n private readonly graphs = new Map<string, Graph>();\n private readonly configs = new Map<string, MokoshConfig>();\n private readonly workspaceGraphs = new Map<string, WorkspaceGraph>();\n private readonly changeImpactCaches = new Map<string, ChangeImpactCache>();\n private readonly dirtyRoots = new Set<string>();\n private readonly watchers = new Map<string, FSWatcher>();\n private readonly lastAnalyze = new Map<string, LastAnalyzeArgs>();\n\n /**\n * @description Returns `true` if config has already been loaded and applied for `root` this session.\n * @param {string} root - Absolute project root path.\n * @returns {boolean} `true` if config was previously stored for this root.\n */\n isConfigured(root: string): boolean {\n return this.configs.has(root);\n }\n\n /**\n * @description Stores the loaded config for `root` so subsequent tool calls can read it without re-loading.\n * @param {string} root - Absolute project root path.\n * @param {MokoshConfig} config - The parsed config to store.\n */\n storeConfig(root: string, config: MokoshConfig): void {\n this.configs.set(root, config);\n }\n\n /**\n * @description Returns the stored config for `root`, or `undefined` if not yet configured.\n * @param {string} root - Absolute project root path.\n * @returns {MokoshConfig | undefined} The previously stored config, or `undefined`.\n */\n getConfig(root: string): MokoshConfig | undefined {\n return this.configs.get(root);\n }\n\n /**\n * Returns the cached graph for `root`, or builds a new one from `entryPoints`.\n *\n * When a prior graph exists it is forwarded to `createImportMap` for\n * incremental rebuilding — unchanged files are reused based on mtime + size\n * comparison, keeping subsequent calls fast on large codebases.\n */\n async getOrBuild(\n root: string,\n entryPoints: string[],\n coverageMap: Map<string, number> = new Map(),\n ): Promise<Graph> {\n const config = this.configs.get(root);\n const graph = await createImportMap(root, entryPoints, this.graphs.get(root) ?? null, {\n gitStats: config?.gitStats ?? false,\n coverageMap,\n });\n this.graphs.set(root, graph);\n return graph;\n }\n\n /**\n * Returns the cached graph for `root`.\n *\n * @throws {Error} if `analyze` has not been called for this root in the\n * current session — mirrors the tool-level requirement.\n */\n require(root: string): Graph {\n const graph = this.graphs.get(root);\n if (!graph) throw new Error('No graph cached for this root. Call \"analyze\" first.');\n return graph;\n }\n\n /**\n * @description Builds (or returns the cached) workspace graph for a monorepo root.\n * Workspace graphs are never incrementally updated — a fresh build is triggered when\n * the cache is empty for this root.\n */\n async getOrBuildWorkspace(\n root: string,\n options: { packages?: string[]; silent?: boolean; gitStats?: boolean } = {},\n ): Promise<WorkspaceGraph> {\n const cached = this.workspaceGraphs.get(root);\n if (cached) return cached;\n const wg = await createWorkspaceGraph(root, options);\n this.workspaceGraphs.set(root, wg);\n return wg;\n }\n\n /**\n * @description Returns the cached workspace graph for `root`.\n * @throws {Error} if `analyze` has not been called for this monorepo root.\n */\n requireWorkspace(root: string): WorkspaceGraph {\n const wg = this.workspaceGraphs.get(root);\n if (!wg) throw new Error('No workspace graph cached for this root. Call \"analyze\" first.');\n return wg;\n }\n\n /**\n * @description Returns `true` when a workspace graph (not a single-package graph) is cached for `root`.\n * @param {string} root - Absolute monorepo root path to check.\n * @returns {boolean} `true` if a workspace graph exists in the cache for this root.\n */\n hasWorkspace(root: string): boolean {\n return this.workspaceGraphs.has(root);\n }\n\n /**\n * @description Returns the change impact cache for `root`, building it lazily on first access.\n * The cache pre-computes all incoming traversals so `get_change_impact` queries are O(1).\n * Requires a prior `analyze` call to ensure the graph is available.\n * @param root - Absolute project root path.\n * @returns The `ChangeImpactCache` for this root.\n */\n getOrBuildChangeImpact(root: string): ChangeImpactCache {\n const existing = this.changeImpactCaches.get(root);\n if (existing) return existing;\n const graph = this.require(root);\n const cache = buildChangeImpactCache(graph);\n this.changeImpactCaches.set(root, cache);\n return cache;\n }\n\n /**\n * @description Records the arguments used in the last `analyze` call for `root` so the watcher\n * can trigger an incremental rebuild using the same parameters when source files change.\n * @param root - Absolute project root path.\n * @param args - The kind of analysis performed (single-package or workspace) and its options.\n */\n storeLastAnalyze(root: string, args: LastAnalyzeArgs): void {\n this.lastAnalyze.set(root, args);\n }\n\n /**\n * @description Starts an `fs.watch` listener on `root` (recursive, ignoring `node_modules`,\n * `.git`, `dist`, `build`, and `coverage` directories). When any source file changes, marks\n * `root` as dirty so the next query transparently triggers an incremental rebuild.\n * Safe to call multiple times — a second call for the same root is a no-op.\n * @param root - Absolute path of the directory to watch.\n */\n startWatching(root: string): void {\n if (this.watchers.has(root)) return;\n try {\n const watcher = fs.watch(root, { recursive: true }, (_event, filename) => {\n if (!filename || IGNORE_WATCH.test(filename)) return;\n this.dirtyRoots.add(root);\n });\n watcher.on(\"error\", () => {\n this.watchers.delete(root);\n });\n this.watchers.set(root, watcher);\n } catch {\n // Degrade gracefully on unsupported filesystems or permission errors.\n }\n }\n\n /**\n * @description Returns a fresh graph for `root`, rebuilding incrementally if source files changed\n * since the last `analyze` call. Acts as a drop-in replacement for `require` in query handlers.\n * @param root - Absolute project root path.\n * @returns The up-to-date `Graph` for this root.\n * @throws {Error} if `analyze` has never been called for this root.\n */\n async ensureFresh(root: string): Promise<Graph> {\n if (!this.dirtyRoots.has(root)) return this.require(root);\n this.dirtyRoots.delete(root);\n this.changeImpactCaches.delete(root);\n const args = this.lastAnalyze.get(root);\n if (args?.kind === \"single\") {\n return this.getOrBuild(root, args.entryPoints, args.coverageMap);\n }\n return this.require(root);\n }\n\n /**\n * @description Returns a fresh workspace graph for `root`, rebuilding if source files changed.\n * Acts as a drop-in replacement for `requireWorkspace` in workspace query handlers.\n * @param root - Absolute monorepo root path.\n * @returns The up-to-date `WorkspaceGraph` for this root.\n * @throws {Error} if `analyze` has never been called for this root.\n */\n async ensureFreshWorkspace(root: string): Promise<WorkspaceGraph> {\n if (!this.dirtyRoots.has(root)) return this.requireWorkspace(root);\n this.dirtyRoots.delete(root);\n this.changeImpactCaches.delete(root);\n this.workspaceGraphs.delete(root);\n const config = this.configs.get(root);\n return this.getOrBuildWorkspace(root, { gitStats: config?.gitStats ?? false });\n }\n\n /**\n * @description Drops the cached graph, workspace graph, and change impact cache for `root`,\n * forcing the next `analyze` call to rebuild from disk. Config is preserved. Use after\n * editing source files mid-session to ensure subsequent queries reflect the updated state.\n * @param root - Absolute path of the project root to invalidate.\n * @returns `true` if a cached graph existed and was removed, `false` if nothing was cached.\n */\n invalidate(root: string): boolean {\n const had = this.graphs.has(root) || this.workspaceGraphs.has(root);\n this.graphs.delete(root);\n this.workspaceGraphs.delete(root);\n this.changeImpactCaches.delete(root);\n this.dirtyRoots.delete(root);\n return had;\n }\n}\n","/** Loads and applies mokosh.config.* files, activating user-defined matchers, patterns, and thresholds. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n setBarrelThreshold,\n} from \"./parser/classify\";\nimport type { TagFramework } from \"./tags/strategies\";\n\n/**\n * @description Top-level configuration for mokosh. All fields are optional; unset fields\n * fall back to built-in defaults. Load this object via `loadMokoshConfig`, then activate\n * it with `applyConfig` before calling `createImportMap`.\n */\nexport interface MokoshConfig {\n /** Additional directories to skip when scanning (merged with built-in defaults). */\n ignoreDirs?: string[];\n /** Additional file extensions to scan (merged with built-in defaults). */\n extensions?: string[];\n /** Override the default cache path (`mokosh-cache/graph.json`). */\n cachePath?: string;\n /** Default entry points used when none are provided on the CLI. */\n entryPoints?: string[];\n /** Additional basename substrings that mark a file as `\"config\"` category. */\n configMatchers?: string[];\n /** Additional basename substrings that mark a file as `\"test\"` category (e.g. `\".unit.\"`). */\n testPatterns?: string[];\n /** Additional import specifiers that indicate a test file (e.g. `\"@my-org/test-utils\"`). */\n testLibraries?: string[];\n /** Ratio of export-statements to total statements required for `\"barrel\"` classification. Default: `0.8`. */\n barrelThreshold?: number;\n /** When true, enriches each node with `commitCount90d` and `lastAuthor` via git log. Only fetched for new/modified files. */\n gitStats?: boolean;\n /**\n * Tag-applier configuration for `--apply-tags`. Controls which format is written into\n * test files. Defaults to `{ framework: \"vitest\" }` when unset.\n */\n tagApplier?: {\n /**\n * Fallback test framework whose tag format to use for TS/JS files. Each file's actual\n * framework is auto-detected from its imports (`@playwright/test`, `cypress`,\n * `@jest/globals`, `vitest`), so a single repo can mix frameworks and each file is tagged\n * in its own native format. This value is only used when a file has no detectable\n * framework import (e.g. `globals: true` configs with no explicit import).\n * - `\"vitest\"` — injects `{ tags: [...] }` in describe/test/it options (default)\n * - `\"playwright\"` — injects `{ tag: [\"@name\"] }` with `@` prefix convention\n * - `\"cypress\"` — injects `{ tags: [\"@name\"] }` for use with `@cypress/grep`\n * - `\"jest\"` — writes a `/** @group name *\\/` docblock for use with `jest-runner-groups`\n */\n framework?: TagFramework;\n /**\n * Path-glob pattern (project-relative, e.g. `\"tests/e2e/**\"`) to fallback framework. Checked\n * in object key order, first match wins, before falling back further to `framework`. Only\n * consulted when a file's own imports don't reveal a framework — lets different directories\n * default to different frameworks (e.g. e2e tests using Playwright globals, unit tests using\n * Jest globals) instead of sharing one project-wide default.\n */\n frameworkOverrides?: Record<string, TagFramework>;\n };\n /** Path to the Istanbul/v8 `coverage-summary.json` file, relative to the project root. When set, `coveragePct` is populated on each node after the graph is built. */\n coverageReportPath?: string;\n /** Default line-coverage threshold (0–100) used by `find_uncovered`. Defaults to `80` when not specified. */\n coverageThreshold?: number;\n}\n\nconst CONFIG_FILENAMES = [\"mokosh.config.js\", \"mokosh.config.cjs\", \"mokosh.config.json\"];\n\n/**\n * @description Loads a mokosh config file, probing standard filenames in `rootDirOrPath` or reading an explicit path when `isExplicitPath` is true.\n * JS/CJS configs may export a plain object or a factory function; the MCP server passes `allowJs: false` to prevent arbitrary code execution.\n * @param {string} rootDirOrPath - Directory to probe for standard config filenames, or absolute path to the config file when `isExplicitPath` is true.\n * @param {{ allowJs?: boolean; isExplicitPath?: boolean }} options - `allowJs` (default `true`) controls whether `.js`/`.cjs` files are loaded; `isExplicitPath` treats the first arg as a direct file path.\n * @returns {MokoshConfig} The parsed config, or an empty object when no config file is found.\n */\nexport function loadMokoshConfig(\n rootDirOrPath: string,\n { allowJs = true, isExplicitPath = false }: { allowJs?: boolean; isExplicitPath?: boolean } = {},\n): MokoshConfig {\n if (isExplicitPath) {\n const filePath = path.resolve(rootDirOrPath);\n if (!fs.existsSync(filePath)) return {};\n if (filePath.endsWith(\".json\")) return readJsonConfig(filePath);\n if (allowJs) return readJsConfig(filePath);\n return {};\n }\n\n for (const filename of CONFIG_FILENAMES) {\n const filePath = path.resolve(rootDirOrPath, filename);\n if (!fs.existsSync(filePath)) continue;\n if (filename.endsWith(\".json\")) return readJsonConfig(filePath);\n if (allowJs) return readJsConfig(filePath);\n }\n\n return {};\n}\n\n/** Parses a JSON config file into a `MokoshConfig`. */\nfunction readJsonConfig(filePath: string): MokoshConfig {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as MokoshConfig;\n}\n\n/**\n * @description Requires a JS/CJS config file and normalises its export.\n * Unwraps `.default` for ESM-interop, and calls the export if it is a factory function.\n * @param {string} filePath - Absolute path to the `.js` or `.cjs` config file\n * @returns {MokoshConfig} The resolved config object\n */\nfunction readJsConfig(filePath: string): MokoshConfig {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n let exported = require(filePath) as MokoshConfig | ((defaults: MokoshConfig) => MokoshConfig);\n if (exported && typeof exported === \"object\" && \"default\" in exported) {\n exported = (exported as { default: typeof exported }).default;\n }\n return typeof exported === \"function\" ? exported({}) : (exported as MokoshConfig);\n}\n\n/**\n * @description Applies a `MokoshConfig` to the global registries that control classification and scanning.\n * Call this after `loadMokoshConfig` and before `createImportMap`.\n * @param {MokoshConfig} config - The loaded config whose matchers, patterns, libraries, and thresholds are registered.\n */\nexport function applyConfig(config: MokoshConfig): void {\n for (const pattern of config.configMatchers ?? []) {\n registerConfigMatcher(pattern);\n }\n for (const pattern of config.testPatterns ?? []) {\n registerTestPattern(pattern);\n }\n for (const lib of config.testLibraries ?? []) {\n registerTestLibrary(lib);\n }\n if (config.barrelThreshold !== undefined) {\n setBarrelThreshold(config.barrelThreshold);\n }\n}\n","/** A config matcher: substring, regex, or predicate tested against the lowercase basename. */\nexport type ConfigMatcher = string | RegExp | ((baseName: string) => boolean);\n\nconst builtinConfigMatchers: ConfigMatcher[] = [\n \".config.\",\n \"biome.json\",\n \"tsconfig.json\",\n \"package.json\",\n \".prettierrc\",\n \".eslintrc\",\n];\n\nconst userConfigMatchers: ConfigMatcher[] = [];\n\n/**\n * @description Registers a custom config-file matcher used when categorising nodes.\n * Accepts a substring, regex, or predicate tested against the lowercase basename.\n * Call this before running `createImportMap` — e.g. in a `mokosh.config.ts`.\n * @param matcher - A substring, `RegExp`, or predicate function tested against the lowercase file basename.\n * @example\n * // Match any file whose basename contains \".myconfig.\"\n * registerConfigMatcher(\".myconfig.\");\n *\n * // Match via regex\n * registerConfigMatcher(/^vite\\.config\\./);\n *\n * // Match via predicate\n * registerConfigMatcher((name) => name.startsWith(\"jest.config\"));\n */\nexport function registerConfigMatcher(matcher: ConfigMatcher): void {\n userConfigMatchers.push(matcher);\n}\n\n/**\n * @description Tests a lowercase basename against all built-in and user-registered config matchers.\n * @param baseName - The lowercase file basename to test, e.g. `\"tsconfig.json\"`.\n * @returns `true` if any registered matcher matches the basename.\n */\nexport function isConfigFile(baseName: string): boolean {\n return [...builtinConfigMatchers, ...userConfigMatchers].some((matcher) => {\n if (typeof matcher === \"string\") return baseName.includes(matcher);\n if (matcher instanceof RegExp) return matcher.test(baseName);\n return matcher(baseName);\n });\n}\n\n// ─── Test-pattern registry ────────────────────────────────────────────────────\n\nconst builtinTestPatterns: string[] = [\".test.\", \".spec.\", \"-test.\", \"-spec.\"];\nconst userTestPatterns: string[] = [];\n\n/**\n * @description Registers an additional basename substring that marks a file as a test.\n * @param pattern - A substring matched against the file basename, e.g. `\".unit.\"`.\n */\nexport function registerTestPattern(pattern: string): void {\n userTestPatterns.push(pattern);\n}\n\n/**\n * @description Returns all test-file basename patterns (built-in + user-registered).\n * @returns Combined array of substring patterns used to identify test files by basename.\n */\nexport function getTestPatterns(): string[] {\n return [...builtinTestPatterns, ...userTestPatterns];\n}\n\n// ─── Testing-library registry ─────────────────────────────────────────────────\n\nconst builtinTestLibraries: string[] = [\n \"jest\",\n \"vitest\",\n \"playwright\",\n \"cypress\",\n \"@testing-library/\",\n];\nconst userTestLibraries: string[] = [];\n\n/**\n * @description Registers an additional import specifier that indicates a test file.\n * @param lib - An import specifier substring, e.g. `\"@my-org/test-utils\"`.\n */\nexport function registerTestLibrary(lib: string): void {\n userTestLibraries.push(lib);\n}\n\n/**\n * @description Returns all testing-library import prefixes (built-in + user-registered).\n * @returns Combined array of import specifier substrings used to detect test files by their imports.\n */\nexport function getTestLibraries(): string[] {\n return [...builtinTestLibraries, ...userTestLibraries];\n}\n\n// ─── Barrel-threshold registry ────────────────────────────────────────────────\n\nlet currentBarrelThreshold = 0.8;\n\n/**\n * @description Sets the minimum ratio of export-statements to total statements required\n * to classify a file as a barrel. Default is `0.8` (80%).\n * @param threshold - A value between 0 and 1; files where exports exceed this fraction of all statements are classified as barrels.\n */\nexport function setBarrelThreshold(threshold: number): void {\n currentBarrelThreshold = threshold;\n}\n\n/**\n * @description Returns the current barrel-detection threshold.\n * @returns The ratio (0–1) above which a file is classified as a barrel.\n */\nexport function getBarrelThreshold(): number {\n return currentBarrelThreshold;\n}\n","export const DEFAULT_IGNORE_DIRS: readonly string[] = [\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n];\n\nexport const DEFAULT_EXTENSIONS: readonly string[] = [\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".css\",\n \".scss\",\n \".sass\",\n \".less\",\n \".styl\",\n \".coffee\",\n \".ls\",\n \".lua\",\n \".py\",\n \".go\",\n \".feature\",\n];\n\nexport interface ScanOptions {\n /** Replaces the default ignore-dir list. Use `additionalIgnoreDirs` to extend instead. */\n ignoreDirs?: string[];\n /** Replaces the default extension list. Use `additionalExtensions` to extend instead. */\n extensions?: string[];\n /** Merged with `DEFAULT_IGNORE_DIRS` (additive). */\n additionalIgnoreDirs?: string[];\n /** Merged with `DEFAULT_EXTENSIONS` (additive). */\n additionalExtensions?: string[];\n}","/** Reads Istanbul/v8 coverage-summary.json and returns a map of file paths to line-coverage percentages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\ninterface CoverageSummaryEntry {\n lines?: { pct?: number };\n [key: string]: unknown;\n}\n\n/**\n * @description Reads an Istanbul/v8 `coverage-summary.json` file and returns a map of\n * project-relative file paths to their line-coverage percentage (0–100).\n * Returns an empty map when the file is missing, unreadable, or malformed — the\n * caller can always proceed safely with no coverage data.\n * @param rootDir - Absolute path to the project root; used to make paths relative.\n * @param reportPath - Path to the coverage summary JSON, relative to `rootDir`.\n * @returns A map of `relativePath → lineCoveragePct`.\n */\nexport function loadCoverageMap(rootDir: string, reportPath: string): Map<string, number> {\n const absoluteReport = path.resolve(rootDir, reportPath);\n try {\n const raw = fs.readFileSync(absoluteReport, \"utf-8\");\n const summary = JSON.parse(raw) as Record<string, CoverageSummaryEntry>;\n const map = new Map<string, number>();\n for (const [absPath, entry] of Object.entries(summary)) {\n if (absPath === \"total\") continue;\n const pct = entry?.lines?.pct;\n if (typeof pct !== \"number\") continue;\n const relative = path.relative(rootDir, absPath);\n map.set(relative, pct);\n }\n return map;\n } catch {\n return new Map();\n }\n}\n","/** GraphExporter implementation that renders dependency graphs as Mermaid flowchart diagrams. */\nimport type { Graph } from \"../graph\";\nimport type { GraphExporter } from \"./types\";\n\n/**\n * @description GraphExporter implementation that renders dependency graphs as Mermaid diagrams.\n * Use this directly or pass it anywhere a GraphExporter is accepted.\n */\nexport const MermaidExporter: GraphExporter = {\n /**\n * @description Serializes the dependency graph into a Mermaid `graph TD` diagram,\n * rendering import edges as arrows and style imports with a labelled edge variant.\n * @param graph - The dependency graph whose nodes and edges to serialize.\n * @returns A Mermaid diagram string starting with `graph TD`.\n */\n serialize(graph: Graph): string {\n const lines: string[] = [\"graph TD\"];\n const visitedEdges = new Set<string>();\n\n for (const node of graph.nodes.values()) {\n const nodeLabel = `\"${node.path}\"`;\n for (const imp of node.imports) {\n if (!imp.toPath) continue;\n const targetLabel = `\"${imp.toPath}\"`;\n const edgeKey = `${node.path} -> ${imp.toPath}`;\n\n if (!visitedEdges.has(edgeKey)) {\n const edgeStyle = imp.isStyle ? \"-- styles -->\" : \"-->\";\n lines.push(` ${nodeLabel} ${edgeStyle} ${targetLabel}`);\n visitedEdges.add(edgeKey);\n }\n }\n }\n return lines.join(\"\\n\");\n },\n};\n\n/**\n * @description Convenience wrapper around MermaidExporter.serialize.\n * @param graph - The dependency graph to render.\n * @returns A Mermaid `graph TD` diagram string.\n */\nexport function toMermaid(graph: Graph): string {\n return MermaidExporter.serialize(graph);\n}\n","/** Detects entry-point files and builds an API surface describing all public exports reachable from them. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { ExportedSymbol } from \"../types/node\";\nimport type { Graph } from \"./model\";\n\n/**\n * Coarse kind of a public export derived from its type signature prefix.\n * Used to distinguish runtime values from type-only exports without parsing the full signature.\n */\nexport type ExportKind =\n | \"function\"\n | \"class\"\n | \"interface\"\n | \"type\"\n | \"enum\"\n | \"const\"\n | \"namespace\"\n | \"unknown\";\n\n/** A single named export surfaced by an entry point, resolved to its original defining file. */\nexport interface PublicExport {\n /** Exported symbol name. */\n name: string;\n /** Project-relative path of the file that originally defines this symbol. */\n definedIn: string;\n /** Coarse kind derived from the signature prefix. */\n kind: ExportKind;\n /** JSDoc summary when present on the defining export. */\n doc?: string;\n /** Type signature string when present (e.g. `\"interface FileNode\"`, `\"class Graph\"`). */\n signature?: string;\n}\n\n/** The complete API surface report for one or more entry points. */\nexport interface ApiSurface {\n /** Project-relative paths used as public entry points for this report. */\n entryPoints: string[];\n /** All symbols accessible from any entry point via direct declaration or `export *` chains. */\n publicExports: PublicExport[];\n /**\n * All non-test files transitively reachable from any entry point (excluding the entry points\n * themselves). These form the implementation surface backing the public API.\n */\n internalFiles: string[];\n /**\n * Non-test files NOT reachable from any entry point — separate consumers (CLI, MCP server),\n * config, or truly unused files. Not automatically dead code.\n */\n unreachableFromEntry: string[];\n /**\n * Test files in the graph that are not reachable from any entry point.\n * Shown separately so they don't inflate the `unreachableFromEntry` signal.\n */\n testFiles: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves a `package.json` field value (from `exports` or `main`) to a project-relative\n * path present in the graph. Tries the field as-is, then converts `dist/…js` → `src/…ts`.\n *\n * @param {string} field - Raw field value (e.g. `\"./dist/index.js\"`).\n * @param {Graph} graph - Graph to probe.\n * @returns {string | null} Project-relative path, or `null` if not found.\n */\nfunction tryResolveSrcEquiv(field: string, graph: Graph): string | null {\n const rel = field.replace(/^\\.\\//, \"\");\n if (graph.nodes.has(rel)) return rel;\n const srcEquiv = rel.replace(/^dist\\//, \"src/\").replace(/\\.(js|mjs|cjs)$/, \".ts\");\n if (graph.nodes.has(srcEquiv)) return srcEquiv;\n return null;\n}\n\n/**\n * Resolves a single value from `package.json exports[subpath]` to a graph path.\n * Handles both plain strings and conditional-export objects (`{ import, require, default }`).\n *\n * @param {unknown} value - Value for one subpath entry in the `exports` map.\n * @param {Graph} graph - Graph to probe.\n * @returns {string | null} Project-relative path, or `null` if not resolvable.\n */\nfunction resolveExportsValue(value: unknown, graph: Graph): string | null {\n if (typeof value === \"string\") return tryResolveSrcEquiv(value, graph);\n if (value && typeof value === \"object\") {\n // Conditional exports: prefer import > require > default\n const cond = value as Record<string, unknown>;\n for (const key of [\"import\", \"require\", \"default\"]) {\n const resolved = resolveExportsValue(cond[key], graph);\n if (resolved) return resolved;\n }\n }\n return null;\n}\n\n/**\n * Infers a coarse `ExportKind` from the leading keyword of a type signature string.\n *\n * @param {string | undefined} signature - Raw signature string from an `ExportedSymbol`.\n * @returns {ExportKind} Inferred kind, or `\"unknown\"` when the signature is absent or unrecognised.\n */\nfunction inferExportKind(signature: string | undefined): ExportKind {\n if (!signature) return \"unknown\";\n const trimmed = signature.trimStart();\n if (trimmed.startsWith(\"interface \")) return \"interface\";\n if (trimmed.startsWith(\"class \")) return \"class\";\n if (trimmed.startsWith(\"enum \")) return \"enum\";\n if (trimmed.startsWith(\"type \")) return \"type\";\n if (trimmed.startsWith(\"namespace \")) return \"namespace\";\n if (\n trimmed.startsWith(\"const \") ||\n trimmed.startsWith(\"let \") ||\n trimmed.startsWith(\"var \") ||\n trimmed.startsWith(\"readonly \")\n )\n return \"const\";\n // Function signatures: leading `(`, async keyword, or contains `=>`\n if (\n trimmed.startsWith(\"(\") ||\n trimmed.startsWith(\"async \") ||\n trimmed.startsWith(\"function \") ||\n trimmed.includes(\"=>\")\n )\n return \"function\";\n return \"unknown\";\n}\n\n/**\n * Walks the `export * from` and named `export { … } from` chains starting at each entry\n * point and returns every symbol name accessible to consumers of those entry points.\n *\n * Wildcard re-exports (`export * from \"./module\"` — edge with no `symbols`) propagate all\n * exports of the target file and recurse into that file's own re-export edges.\n * Named re-exports (`export { foo } from \"./module\"` — edge with `symbols: [\"foo\"]`) add\n * only those names without recursing, because the constraint is already fully specified.\n *\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {Set<string>} All symbol names accessible from the entry points.\n */\nfunction collectAccessibleSymbolNames(graph: Graph, entryPoints: string[]): Set<string> {\n const accessible = new Set<string>();\n // Only visit a file via wildcard path once to avoid cycles and redundant work\n const wildcardVisited = new Set<string>();\n const queue: string[] = [...entryPoints];\n\n while (queue.length) {\n const current = queue.shift() as string;\n if (wildcardVisited.has(current)) continue;\n wildcardVisited.add(current);\n\n const node = graph.nodes.get(current);\n if (!node) continue;\n\n // Direct exports declared in this file (catches concrete declarations in entry points)\n for (const sym of node.exports) accessible.add(sym.name);\n\n // Follow re-export edges.\n // The TypeScript parser represents `export * from \"…\"` as symbols: [\"*\"].\n // Named re-exports like `export { foo } from \"…\"` carry the actual names.\n for (const imp of node.imports) {\n if (imp.type !== \"re-export\" || imp.isExternal || !imp.toPath) continue;\n\n const isWildcard = !imp.symbols?.length || imp.symbols.includes(\"*\");\n if (isWildcard) {\n // Wildcard re-export: expose all target exports and recurse into that file\n const target = graph.nodes.get(imp.toPath);\n if (target) {\n for (const sym of target.exports) accessible.add(sym.name);\n }\n queue.push(imp.toPath);\n } else {\n // Named re-export: expose only the listed names, do not recurse\n for (const name of imp.symbols as string[]) accessible.add(name);\n }\n }\n }\n\n return accessible;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Attempts to auto-detect the primary public entry point by reading `package.json` from `root`.\n * Handles modern conditional-exports objects as well as plain `main`/`module` fields.\n * Converts `dist/index.js` → `src/index.ts` before checking the graph.\n * Falls back to common candidates when `package.json` is absent or unparseable.\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string} root - Absolute path to the project root.\n * @returns {string | null} Project-relative path of the detected entry point, or `null` if none found.\n */\nexport function detectEntryPoint(graph: Graph, root: string): string | null {\n const all = detectAllEntryPoints(graph, root);\n return all[0] ?? null;\n}\n\n/**\n * Detects all public entry points for a project by reading the `package.json exports` map.\n * Each sub-path (`.`, `./utils`, etc.) is resolved to a project-relative graph path.\n * Falls back to `main`/`module` fields, then to common `src/index.ts` candidates.\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string} root - Absolute path to the project root.\n * @returns {string[]} Ordered list of project-relative paths for all detected entry points.\n */\nexport function detectAllEntryPoints(graph: Graph, root: string): string[] {\n const found: string[] = [];\n\n const pkgPath = path.join(root, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\")) as {\n main?: string;\n module?: string;\n exports?: unknown;\n };\n\n // Modern packages: parse exports map (handles conditional exports)\n if (pkg.exports && typeof pkg.exports === \"object\" && !Array.isArray(pkg.exports)) {\n for (const value of Object.values(pkg.exports as Record<string, unknown>)) {\n const resolved = resolveExportsValue(value, graph);\n if (resolved && !found.includes(resolved)) found.push(resolved);\n }\n } else if (typeof pkg.exports === \"string\") {\n const resolved = tryResolveSrcEquiv(pkg.exports, graph);\n if (resolved) found.push(resolved);\n }\n\n // Legacy fallbacks: main / module\n if (found.length === 0) {\n for (const field of [pkg.main, pkg.module].filter(Boolean) as string[]) {\n const resolved = tryResolveSrcEquiv(field, graph);\n if (resolved && !found.includes(resolved)) found.push(resolved);\n }\n }\n } catch {\n // ignore parse/IO errors\n }\n }\n\n // Last resort: well-known candidates\n if (found.length === 0) {\n for (const candidate of [\"src/index.ts\", \"src/index.js\", \"index.ts\", \"index.js\"]) {\n if (graph.nodes.has(candidate)) {\n found.push(candidate);\n break;\n }\n }\n }\n\n return found;\n}\n\n/**\n * Builds an API surface report for one or more public entry points.\n *\n * **Public exports** are collected by walking `export * from` wildcard chains and named\n * `export { … } from` edges — not just the `exports` array of the entry node. This means\n * barrel re-export patterns (the common TypeScript library layout) are handled correctly.\n * Each symbol is resolved to the file that concretely defines it (has `signature` or `doc`\n * on a non-barrel node); barrel intermediaries are skipped.\n *\n * **File partitioning** (all graph nodes, each in exactly one bucket):\n * - `entryPoints` themselves\n * - `internalFiles` — reachable from any entry point, non-test\n * - `testFiles` — not reachable from any entry point, `category === \"test\"`\n * - `unreachableFromEntry` — not reachable from any entry point, non-test (may be separate consumers or dead code)\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of the public entry point files.\n * @returns {ApiSurface} The API surface report.\n * @throws {Error} If any entry point is not present in the graph.\n */\n/**\n * @description Walks outgoing imports from every entry point and returns the set of all\n * files reachable from them, including the entry points themselves.\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {Set<string>} Every file path reachable from any entry point.\n */\nfunction collectReachableFiles(graph: Graph, entryPoints: string[]): Set<string> {\n const reachableFiles = new Set<string>(entryPoints);\n for (const entryPoint of entryPoints) {\n graph.traverse(\n entryPoint,\n (node) => {\n reachableFiles.add(node.path);\n return true;\n },\n { direction: \"outgoing\" },\n );\n }\n return reachableFiles;\n}\n\n/** The best concrete definition found for an exported symbol name. */\ninterface SymbolDefinition {\n file: string;\n symbol: ExportedSymbol;\n}\n\n/**\n * @description Builds a map of symbol name → best concrete definition among all reachable,\n * non-entry files. \"Best\" means having a signature/doc on a non-barrel file, so `definedIn`\n * points at the actual implementation rather than a re-exporting barrel.\n * @param {Graph} graph - The dependency graph.\n * @param {Set<string>} reachableFiles - Files reachable from any entry point.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points, excluded from consideration.\n * @returns {Map<string, SymbolDefinition>} Symbol name → its best concrete definition.\n */\nfunction buildDefinitionsMap(\n graph: Graph,\n reachableFiles: Set<string>,\n entryPoints: string[],\n): Map<string, SymbolDefinition> {\n const definitions = new Map<string, SymbolDefinition>();\n for (const filePath of reachableFiles) {\n if (entryPoints.includes(filePath)) continue;\n const node = graph.nodes.get(filePath);\n if (!node) continue;\n const isBarrel = node.category === \"barrel\";\n for (const exportedSymbol of node.exports) {\n const existingDefinition = definitions.get(exportedSymbol.name);\n const hasConcreteSignature = !!(exportedSymbol.signature || exportedSymbol.doc);\n if (!existingDefinition || (hasConcreteSignature && !isBarrel)) {\n definitions.set(exportedSymbol.name, { file: filePath, symbol: exportedSymbol });\n }\n }\n }\n return definitions;\n}\n\n/**\n * @description Builds the sorted `publicExports` list for every accessible symbol name,\n * preferring the best concrete definition found by `buildDefinitionsMap` and falling back\n * to the entry node's own `ExportedSymbol` when no better definition exists.\n * @param {Set<string>} accessibleNames - All symbol names accessible from the entry points.\n * @param {Map<string, SymbolDefinition>} definitions - Symbol name → best concrete definition.\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {PublicExport[]} Public exports sorted alphabetically by name.\n */\nfunction buildPublicExports(\n accessibleNames: Set<string>,\n definitions: Map<string, SymbolDefinition>,\n graph: Graph,\n entryPoints: string[],\n): PublicExport[] {\n const publicExports: PublicExport[] = [];\n for (const name of accessibleNames) {\n const definition = definitions.get(name);\n const entrySymbol = entryPoints\n .flatMap((entryPoint) => graph.nodes.get(entryPoint)?.exports ?? [])\n .find((exportedSymbol) => exportedSymbol.name === name);\n const symbol = definition?.symbol ?? entrySymbol;\n\n const definedIn =\n definition?.file ??\n entryPoints.find((entryPoint) =>\n graph.nodes.get(entryPoint)?.exports.some((exportedSymbol) => exportedSymbol.name === name),\n ) ??\n (entryPoints[0] as string);\n\n const publicExport: PublicExport = {\n name,\n definedIn,\n kind: inferExportKind(symbol?.signature),\n };\n if (symbol?.doc) publicExport.doc = symbol.doc;\n if (symbol?.signature) publicExport.signature = symbol.signature;\n publicExports.push(publicExport);\n }\n publicExports.sort((exportA, exportB) => exportA.name.localeCompare(exportB.name));\n return publicExports;\n}\n\n/** File-path partitions of the whole graph relative to reachability and test status. */\ninterface NodePartitions {\n internalFiles: string[];\n unreachableFromEntry: string[];\n testFiles: string[];\n}\n\n/**\n * @description Partitions every file in the graph into implementation files backing the\n * public API (`internalFiles`), non-test files unreachable from any entry point\n * (`unreachableFromEntry`), and unreachable test files (`testFiles`).\n * @param {Graph} graph - The dependency graph.\n * @param {Set<string>} reachableFiles - Files reachable from any entry point.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {NodePartitions} The three file-path partitions.\n */\nfunction partitionNodes(\n graph: Graph,\n reachableFiles: Set<string>,\n entryPoints: string[],\n): NodePartitions {\n const isTestNode = (filePath: string) => graph.nodes.get(filePath)?.category === \"test\";\n\n const internalFiles = [...reachableFiles].filter(\n (filePath) => !entryPoints.includes(filePath) && !isTestNode(filePath),\n );\n\n const unreachableFiles = [...graph.nodes.keys()].filter(\n (filePath) => !reachableFiles.has(filePath),\n );\n const unreachableFromEntry = unreachableFiles.filter((filePath) => !isTestNode(filePath));\n const testFiles = unreachableFiles.filter((filePath) => isTestNode(filePath));\n\n return { internalFiles, unreachableFromEntry, testFiles };\n}\n\nexport function buildApiSurface(graph: Graph, entryPoints: string[]): ApiSurface {\n if (entryPoints.length === 0)\n throw new Error(\"buildApiSurface requires at least one entry point\");\n\n for (const entryPoint of entryPoints) {\n if (!graph.nodes.has(entryPoint))\n throw new Error(`Entry point not found in graph: ${entryPoint}`);\n }\n\n const reachableFiles = collectReachableFiles(graph, entryPoints);\n const definitions = buildDefinitionsMap(graph, reachableFiles, entryPoints);\n\n // Handles `export * from` wildcards that the parser doesn't expand into the entry node's\n // own `exports` array.\n const accessibleNames = collectAccessibleSymbolNames(graph, entryPoints);\n\n const publicExports = buildPublicExports(accessibleNames, definitions, graph, entryPoints);\n const { internalFiles, unreachableFromEntry, testFiles } = partitionNodes(\n graph,\n reachableFiles,\n entryPoints,\n );\n\n return { entryPoints, publicExports, internalFiles, unreachableFromEntry, testFiles };\n}\n","/** Queries the call-edge graph to find callers and callees at the function level. */\nimport type { Graph } from \"../model\";\nimport type { CalleeEntry, CallerEntry, FunctionCallInfo } from \"./types\";\n\nexport type { CalleeEntry, CallerEntry, FunctionCallInfo } from \"./types\";\n\n/**\n * Queries the call graph for a named function, returning its callers and callees.\n *\n * Callers are found by scanning every node's `callEdges` for edges whose `to`\n * field matches `functionName`. Callees are found by looking at the defining\n * file's `callEdges` for edges whose `from` field matches `functionName`.\n *\n * Call edges are populated only for TypeScript/JavaScript files. Functions in\n * other language files will return empty `callers` and `callees` arrays.\n *\n * @param {Graph} graph - The import graph that carries `callEdges` on each node.\n * @param {string} functionName - Exact name of the function to look up.\n * @returns {FunctionCallInfo} Caller/callee lists; `definedIn` is `null` if the function is not exported.\n */\nexport function queryCallGraph(graph: Graph, functionName: string): FunctionCallInfo {\n let definedIn: string | null = null;\n const callers: CallerEntry[] = [];\n\n for (const node of graph.nodes.values()) {\n if (node.exports.some((exportedSym) => exportedSym.name === functionName)) {\n definedIn = node.path;\n }\n\n for (const edge of node.callEdges ?? []) {\n if (edge.to === functionName) {\n callers.push({ file: node.path, callerFunction: edge.from });\n }\n }\n }\n\n const callees: CalleeEntry[] = [];\n if (definedIn) {\n const defNode = graph.nodes.get(definedIn);\n for (const edge of defNode?.callEdges ?? []) {\n if (edge.from === functionName) {\n callees.push({ file: edge.toFile, calleeFunction: edge.to });\n }\n }\n }\n\n return { functionName, definedIn, callers, callees };\n}\n","/** Pre-computed blast-radius cache: maps each file to the set of files that would be affected if it changed. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Graph } from \"./model\";\n\n/**\n * Pre-computed blast-radius map for every file in the graph.\n *\n * At small scales (< ~500 nodes) the live `graph.traverse` approach used by\n * `get_affected` is fast enough (< 1ms per query). The cache pays off when:\n * - The same file is queried many times in one session (O(1) vs O(n) per call)\n * - The codebase exceeds ~1000 nodes and traversal cost becomes noticeable\n * - The cache is persisted to disk and reused across MCP restarts\n */\nexport interface ChangeImpactCache {\n /**\n * Map from project-relative file path to the list of all files that are\n * transitively affected if that file changes (incoming traversal).\n */\n impact: Map<string, string[]>;\n /**\n * Fingerprint of the graph this cache was built from.\n * Used to detect stale caches without re-traversing the graph.\n */\n graphHash: string;\n}\n\n/** Wire format written to `.mokosh/change-impact-cache.json`. */\ninterface SerializedChangeImpactCache {\n graphHash: string;\n impact: [string, string[]][];\n}\n\n/**\n * Computes a lightweight fingerprint of the graph by hashing the sorted list of\n * `path:mtime:size` tuples for every node. Any file addition, deletion, or\n * modification will produce a different hash.\n *\n * @param graph - The graph to fingerprint.\n * @returns A hex string hash.\n */\nexport function computeGraphHash(graph: Graph): string {\n const entries = [...graph.nodes.entries()]\n .sort(([pathA], [pathB]) => pathA.localeCompare(pathB))\n .map(([filePath, node]) => `${filePath}:${node.mtime}:${node.size}`)\n .join(\"|\");\n\n // FNV-1a 32-bit — fast, deterministic, good enough for cache invalidation.\n let hash = 0x811c9dc5;\n for (let i = 0; i < entries.length; i++) {\n hash ^= entries.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash.toString(16).padStart(8, \"0\");\n}\n\n/**\n * Pre-computes the incoming blast-radius for every node in the graph.\n *\n * Runs one incoming traversal per node — O(n²) worst case but performed once.\n * Results are stored in a `Map` for O(1) subsequent lookups.\n *\n * @param graph - The import graph to pre-compute.\n * @returns A `ChangeImpactCache` ready for `queryChangeImpact`.\n */\nexport function buildChangeImpactCache(graph: Graph): ChangeImpactCache {\n const impact = new Map<string, string[]>();\n\n for (const filePath of graph.nodes.keys()) {\n const affected: string[] = [];\n graph.traverse(\n filePath,\n (node) => {\n if (node.path !== filePath) affected.push(node.path);\n return true;\n },\n { direction: \"incoming\" },\n );\n impact.set(filePath, affected);\n }\n\n return { impact, graphHash: computeGraphHash(graph) };\n}\n\n/**\n * Returns the list of files transitively affected by a change in `filePath`.\n * Falls back to an empty array when the file is not in the cache.\n *\n * @param cache - A previously built `ChangeImpactCache`.\n * @param filePath - Project-relative path of the changed file.\n * @returns Sorted list of affected file paths.\n */\nexport function queryChangeImpact(cache: ChangeImpactCache, filePath: string): string[] {\n return cache.impact.get(filePath) ?? [];\n}\n\n/**\n * Returns `true` when `cache` was built from the same graph as `graph`.\n * Use this before trusting a deserialized cache loaded from disk.\n *\n * @param cache - The cache to validate.\n * @param graph - The current graph to compare against.\n * @returns `true` if the cache is still valid for this graph.\n */\nexport function isChangeImpactCacheValid(cache: ChangeImpactCache, graph: Graph): boolean {\n return cache.graphHash === computeGraphHash(graph);\n}\n\n/**\n * Serializes a `ChangeImpactCache` to JSON and writes it to `cachePath`,\n * creating parent directories as needed.\n *\n * @param cache - The cache to persist.\n * @param cachePath - Absolute path to write the JSON file.\n */\nexport function saveChangeImpactCache(cache: ChangeImpactCache, cachePath: string): void {\n const dir = path.dirname(cachePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const serialized: SerializedChangeImpactCache = {\n graphHash: cache.graphHash,\n impact: [...cache.impact.entries()],\n };\n fs.writeFileSync(cachePath, JSON.stringify(serialized));\n}\n\n/**\n * Reads and deserializes a `ChangeImpactCache` from disk.\n * Returns `null` when the file does not exist or cannot be parsed.\n *\n * @param cachePath - Absolute path to the JSON file written by `saveChangeImpactCache`.\n * @returns The deserialized cache, or `null` on failure.\n */\nexport function loadChangeImpactCache(cachePath: string): ChangeImpactCache | null {\n if (!fs.existsSync(cachePath)) return null;\n try {\n const raw = fs.readFileSync(cachePath, \"utf-8\");\n const parsed = JSON.parse(raw) as SerializedChangeImpactCache;\n return {\n graphHash: parsed.graphHash,\n impact: new Map(parsed.impact),\n };\n } catch {\n return null;\n }\n}\n","/** Detects feature-hub files — high-out-degree orchestrators that import many other files — from a dependency graph. */\nimport path from \"node:path\";\nimport type { FileNode } from \"../../types/node\";\n\n/** Controls how aggressively `detectFeatures` promotes files to features. */\nexport interface FeatureDetectionOptions {\n /**\n * Minimum number of internal imports a file must have before it is\n * considered a feature. Lower values surface more candidates;\n * higher values keep the list focused on true feature aggregators.\n * @default 5\n */\n minOutDegree?: number;\n}\n\n/**\n * A file identified as a feature hub — a non-test file with high out-degree\n * (imports many internal modules), acting as an orchestrator or aggregator.\n */\nexport interface FeatureInfo {\n /** Absolute (or project-relative) path of the feature file. */\n path: string;\n /** How many internal files this file imports (its out-degree in the dep graph). */\n outDegree: number;\n /** Auto-generated tag of the form `feature:<basename>`, used for queries and reports. */\n tag: string;\n}\n\n/**\n * @description Counts how many internal imports each file has, producing the raw out-degree data\n * used by `buildFeatureMap` to filter feature candidates.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @returns {Map<string, number>} Map from file path to its internal import count (out-degree).\n */\nfunction buildOutDegreeMap(nodes: Map<string, FileNode>): Map<string, number> {\n const outDegreeMap = new Map<string, number>();\n for (const [filePath, node] of nodes) {\n const count = node.imports.filter((imp) => imp.toPath && !imp.isExternal).length;\n if (count > 0) {\n outDegreeMap.set(filePath, count);\n }\n }\n return outDegreeMap;\n}\n\n/**\n * @description Filters an out-degree map down to the non-test, non-barrel files whose import\n * count meets `minOutDegree`, then builds the `FeatureInfo` record for each.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @param {Map<string, number>} outDegreeMap - Pre-computed internal import counts for every file.\n * @param {number} minOutDegree - Minimum out-degree a file must reach to be included.\n * @returns {Map<string, FeatureInfo>} Map of qualifying feature files; empty if none qualify.\n */\nfunction buildFeatureMap(\n nodes: Map<string, FileNode>,\n outDegreeMap: Map<string, number>,\n minOutDegree: number,\n): Map<string, FeatureInfo> {\n const result = new Map<string, FeatureInfo>();\n for (const [filePath, outDegree] of outDegreeMap) {\n if (outDegree < minOutDegree) continue;\n const node = nodes.get(filePath);\n if (!node || node.category === \"test\" || node.category === \"barrel\") continue;\n const ext = path.extname(filePath);\n const basename = path.basename(filePath, ext);\n const label = basename === \"index\" ? path.basename(path.dirname(filePath)) : basename;\n result.set(filePath, { path: filePath, outDegree, tag: `feature:${label}` });\n }\n return result;\n}\n\n/**\n * @description Scans the dependency graph and promotes non-test, non-barrel files with many\n * imports to \"feature hubs\". Uses a two-pass approach: first count out-degrees, then filter\n * and annotate. The result maps file path → `FeatureInfo` for tag generation or graph annotation.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @param {FeatureDetectionOptions} [options] - Tuning knobs; currently just `minOutDegree` (default 5).\n * @returns {Map<string, FeatureInfo>} Map of detected feature files; empty if none qualify.\n */\nexport function detectFeatures(\n nodes: Map<string, FileNode>,\n options?: FeatureDetectionOptions,\n): Map<string, FeatureInfo> {\n const minOutDegree = options?.minOutDegree ?? 5;\n\n return buildFeatureMap(nodes, buildOutDegreeMap(nodes), minOutDegree);\n}\n","/** Builds a FeatureGraph grouping graph nodes into feature domains under their respective hub files. */\nimport type { FileNode } from \"../../types/node\";\nimport type { Graph } from \"../model\";\nimport { detectFeatures, type FeatureDetectionOptions, type FeatureInfo } from \"./index\";\n\n/**\n * A group of files that a single feature hub transitively imports.\n * The hub itself is the high-out-degree orchestrator; `files` are its dependencies.\n */\nexport interface FeatureDomain {\n /** Project-relative path of the feature hub file. */\n hub: string;\n /** Number of internal imports the hub has (its out-degree). */\n outDegree: number;\n /** All files transitively imported by the hub, excluding the hub itself. */\n files: string[];\n}\n\n/**\n * Domain-clustered view of the import graph.\n * Provides a token-efficient way to answer \"what files are in domain X?\"\n * without traversing the full graph.\n */\nexport interface FeatureGraph {\n /** Map from feature name (e.g. `\"parser\"`) to its domain info. */\n features: Map<string, FeatureDomain>;\n /**\n * Files not reachable from any feature hub — shared utilities, top-level\n * entry points, or files below the out-degree threshold.\n */\n unassigned: string[];\n}\n\n/**\n * Options for `buildFeatureGraph`. Extends `FeatureDetectionOptions` so callers\n * can pass `{ minOutDegree }` without needing to know this type explicitly.\n */\nexport interface FeatureGraphOptions extends FeatureDetectionOptions {\n /**\n * Comparator used to pick the \"best\" hub when a file is reachable from\n * multiple hubs. Return a negative number when `left` should win over `right`.\n * @default ascending out-degree (most-specific hub wins)\n */\n hubComparator?: (left: FeatureInfo, right: FeatureInfo) => number;\n /**\n * Override the hub-detection function. Defaults to `detectFeatures`.\n * Inject a custom implementation for testing or alternative hub strategies.\n */\n detectFn?: (\n nodes: Map<string, FileNode>,\n options?: FeatureDetectionOptions,\n ) => Map<string, FeatureInfo>;\n}\n\nconst DEFAULT_HUB_COMPARATOR = (left: FeatureInfo, right: FeatureInfo) =>\n left.outDegree - right.outDegree;\n\n/**\n * @param graph - The import graph to cluster.\n * @param hubs - Detected feature hub files.\n * @returns Map from hub path to the set of files reachable from that hub (hub itself excluded).\n */\nfunction collectReachable(graph: Graph, hubs: Map<string, FeatureInfo>): Map<string, Set<string>> {\n const reachable = new Map<string, Set<string>>();\n for (const hub of hubs.values()) {\n const files = new Set<string>();\n graph.traverse(\n hub.path,\n (node) => {\n if (node.path !== hub.path) files.add(node.path);\n return true;\n },\n { direction: \"outgoing\" },\n );\n reachable.set(hub.path, files);\n }\n return reachable;\n}\n\n/**\n * @param nodes - All graph nodes.\n * @param hubs - Detected feature hub files.\n * @param reachable - Pre-computed reachability sets from `collectReachable`.\n * @param comparator - Tiebreak function; lower return value means the hub wins.\n * @returns Map from non-hub file path to the path of its assigned hub.\n */\nfunction assignFilesToHubs(\n nodes: Map<string, FileNode>,\n hubs: Map<string, FeatureInfo>,\n reachable: Map<string, Set<string>>,\n comparator: (left: FeatureInfo, right: FeatureInfo) => number,\n): Map<string, string> {\n const fileToHub = new Map<string, string>();\n for (const [filePath] of nodes) {\n if (hubs.has(filePath)) continue;\n let bestHub: FeatureInfo | null = null;\n for (const hub of hubs.values()) {\n if (!reachable.get(hub.path)?.has(filePath)) continue;\n if (!bestHub || comparator(hub, bestHub) < 0) bestHub = hub;\n }\n if (bestHub) fileToHub.set(filePath, bestHub.path);\n }\n return fileToHub;\n}\n\n/**\n * @param hubs - Detected feature hub files.\n * @param fileToHub - Assignment map from `assignFilesToHubs`.\n * @returns Map from feature name to its `FeatureDomain`.\n */\nfunction buildDomains(\n hubs: Map<string, FeatureInfo>,\n fileToHub: Map<string, string>,\n): Map<string, FeatureDomain> {\n const features = new Map<string, FeatureDomain>();\n for (const hub of hubs.values()) {\n const featureName = hub.tag.replace(\"feature:\", \"\");\n const files: string[] = [];\n for (const [filePath, ownerHub] of fileToHub) {\n if (ownerHub === hub.path) files.push(filePath);\n }\n features.set(featureName, { hub: hub.path, outDegree: hub.outDegree, files });\n }\n return features;\n}\n\n/**\n * @param nodes - All graph nodes.\n * @param hubs - Detected feature hub files.\n * @param fileToHub - Assignment map from `assignFilesToHubs`.\n * @returns File paths that are neither a hub nor claimed by any hub.\n */\nfunction collectUnassigned(\n nodes: Map<string, FileNode>,\n hubs: Map<string, FeatureInfo>,\n fileToHub: Map<string, string>,\n): string[] {\n const unassigned: string[] = [];\n for (const filePath of nodes.keys()) {\n if (!hubs.has(filePath) && !fileToHub.has(filePath)) {\n unassigned.push(filePath);\n }\n }\n return unassigned;\n}\n\n/**\n * Builds a domain-clustered view of the import graph by grouping files under\n * the most specific feature hub that can reach them.\n *\n * Assignment rule: each file is assigned to the hub with the lowest out-degree\n * that can transitively reach it (overridable via `options.hubComparator`).\n *\n * @param graph - The import graph to cluster.\n * @param options - Controls hub detection threshold, assignment comparator, and detectFn override.\n * @returns A `FeatureGraph` with one domain per hub and an `unassigned` list.\n */\nexport function buildFeatureGraph(graph: Graph, options?: FeatureGraphOptions): FeatureGraph {\n const detectFn = options?.detectFn ?? detectFeatures;\n const comparator = options?.hubComparator ?? DEFAULT_HUB_COMPARATOR;\n const hubs = detectFn(graph.nodes, options);\n const reachable = collectReachable(graph, hubs);\n const fileToHub = assignFilesToHubs(graph.nodes, hubs, reachable, comparator);\n return {\n features: buildDomains(hubs, fileToHub),\n unassigned: collectUnassigned(graph.nodes, hubs, fileToHub),\n };\n}\n","/** Analyzes a dependency graph node map for unused files, export-usage hotspots, and circular import chains. */\nimport type { FileNode } from \"../types/node\";\n\n/**\n * @description Utility for analyzing the dependency graph for cycles and unused files.\n * Operates on the raw node map rather than a `Graph` instance so it can be used\n * without the full traversal infrastructure.\n */\nexport class GraphAnalyzer {\n /**\n * @param {Map<string, FileNode>} nodes - The full node map of the graph to analyze, keyed by project-relative file path.\n */\n constructor(private nodes: Map<string, FileNode>) {}\n\n /**\n * @description Returns files from `allFiles` that are absent from the graph — meaning nothing\n * imports them directly or transitively from any entry point, making them deletion candidates.\n * @param {string[]} allFiles - Complete list of project-relative file paths to test against the graph.\n * @returns {string[]} Subset of `allFiles` whose paths do not appear as graph nodes.\n */\n public findUnusedFiles(allFiles: string[]): string[] {\n const usedFiles = new Set(this.nodes.keys());\n return allFiles.filter((file) => !usedFiles.has(file));\n }\n\n /**\n * @description Returns files whose highest single-edge export usage ratio meets or exceeds\n * `threshold`, sorted descending by `maxExportUsage`. Useful for identifying files\n * that consume a large fraction of one dependency's API surface.\n * @param {number} threshold - Minimum `maxExportUsage` value (0–1) for a file to be included.\n * @returns {Array<{ path: string; maxExportUsage: number; tightestDep: string }>} Entries sorted descending by `maxExportUsage`.\n */\n public findHighExportUsage(\n threshold: number,\n ): Array<{ path: string; maxExportUsage: number; tightestDep: string }> {\n const results: Array<{ path: string; maxExportUsage: number; tightestDep: string }> = [];\n\n for (const node of this.nodes.values()) {\n if (node.maxExportUsage === undefined || node.maxExportUsage < threshold) continue;\n const tightest = node.imports.reduce(\n (best, imp) => ((imp.exportUsageRatio ?? 0) > (best?.exportUsageRatio ?? 0) ? imp : best),\n null as (typeof node.imports)[number] | null,\n );\n results.push({\n path: node.path,\n maxExportUsage: node.maxExportUsage,\n tightestDep: tightest?.toPath ?? \"\",\n });\n }\n\n return results.sort((left, right) => right.maxExportUsage - left.maxExportUsage);\n }\n\n /**\n * @description Detects all circular import chains using DFS with a recursion-stack back-edge check.\n * Each returned array is one cycle as an ordered list of file paths ending at the entry that closes the loop.\n * @returns {string[][]} Array of cycles; each cycle is an ordered list of file paths forming a loop.\n */\n public findCycles(): string[][] {\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const recStack = new Set<string>();\n const currentPath: string[] = [];\n\n const find = (current: string) => {\n visited.add(current);\n recStack.add(current);\n currentPath.push(current);\n\n const node = this.nodes.get(current);\n if (node) {\n for (const imp of node.imports) {\n if (!imp.toPath || imp.isExternal) continue;\n\n if (recStack.has(imp.toPath)) {\n // Found a cycle\n const cycleIndex = currentPath.indexOf(imp.toPath);\n cycles.push([...currentPath.slice(cycleIndex), imp.toPath]);\n } else if (!visited.has(imp.toPath)) {\n find(imp.toPath);\n }\n }\n }\n\n recStack.delete(current);\n currentPath.pop();\n };\n\n for (const nodePath of this.nodes.keys()) {\n if (!visited.has(nodePath)) {\n find(nodePath);\n }\n }\n\n return cycles;\n }\n}\n","/** Graph class wrapping the raw node map with DFS traversal, cycle detection, serialization, and reverse-edge helpers. */\nimport type { SerializedGraph, TraversalOptions, TraversalVisitor } from \"../types/graph\";\nimport type { CallEdge, FileNode } from \"../types/node\";\nimport { GraphAnalyzer } from \"./analyzer\";\n\n/**\n * @description Represents the dependency graph of the project.\n * Wraps the raw node map with traversal, cycle detection, serialization, and\n * reverse-index helpers. All node paths are project-relative strings.\n */\nexport class Graph {\n private _incomingEdgesCache: Map<string, string[]> | null = null;\n private _callIncomingCache: Map<string, string[]> | null = null;\n\n /**\n * @param {Map<string, FileNode>} nodes - All parsed file nodes, keyed by project-relative path.\n */\n constructor(public nodes: Map<string, FileNode>) {}\n\n /**\n * @description Serializes the graph into a plain JSON-compatible object\n * that can be written to disk and later restored via `deserialize`.\n * @returns {SerializedGraph} A flat representation of all nodes in the graph.\n */\n public serialize(): SerializedGraph {\n return {\n nodes: Array.from(this.nodes.values()),\n };\n }\n\n /**\n * @description Reconstructs a Graph from a serialized snapshot, rebuilding\n * the internal node map keyed by file path.\n * @param {SerializedGraph} serialized - The plain object produced by `serialize`.\n * @returns {Graph} A fully functional Graph instance.\n */\n public static deserialize(serialized: SerializedGraph): Graph {\n const nodes = new Map<string, FileNode>();\n for (const node of serialized.nodes) {\n nodes.set(node.path, node);\n }\n return new Graph(nodes);\n }\n\n /**\n * @description Lazily builds and caches a reverse index mapping each file path\n * to the list of files that import it. Used internally for incoming traversal.\n * @returns {Map<string, string[]>} Map from target file path to list of importer file paths.\n */\n private getIncomingEdgesMap(): Map<string, string[]> {\n if (this._incomingEdgesCache) return this._incomingEdgesCache;\n const incoming = new Map<string, string[]>();\n for (const node of this.nodes.values()) {\n for (const imp of node.imports) {\n if (imp.toPath) {\n const list = incoming.get(imp.toPath) || [];\n list.push(node.path);\n incoming.set(imp.toPath, list);\n }\n }\n }\n this._incomingEdgesCache = incoming;\n return incoming;\n }\n\n /**\n * @description Core DFS engine. Visits each reachable node once, calling visitor at each step.\n * The caller provides a getNeighbors function so the same loop works for any edge type.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Called for each visited node; return `false` to prune the branch.\n * @param options - `maxDepth` and `direction` (direction is interpreted by the caller's getNeighbors).\n * @param getNeighbors - Returns the next paths to visit from a given path.\n */\n private dfs(\n startPath: string,\n visitor: TraversalVisitor,\n options: TraversalOptions,\n getNeighbors: (path: string) => string[],\n ) {\n const visited = new Set<string>();\n const maxDepth = options.maxDepth ?? Infinity;\n\n const walk = (currentPath: string, depth: number, parentPath: string | null) => {\n if (depth > maxDepth || visited.has(currentPath)) return;\n const node = this.nodes.get(currentPath);\n if (!node) return;\n visited.add(currentPath);\n if (visitor(node, depth, parentPath) === false) return;\n for (const neighbor of getNeighbors(currentPath)) {\n walk(neighbor, depth + 1, currentPath);\n }\n };\n\n walk(startPath, 0, null);\n }\n\n /**\n * @description Performs a DFS on the import dependency graph.\n * Supports both outgoing and incoming (reverse) traversal.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Callback executed for each node; return `false` to stop traversing a branch.\n * @param options - Configuration for `maxDepth` and `direction`.\n */\n public traverse(startPath: string, visitor: TraversalVisitor, options: TraversalOptions = {}) {\n const direction = options.direction ?? \"outgoing\";\n const incoming = direction === \"incoming\" ? this.getIncomingEdgesMap() : null;\n this.dfs(startPath, visitor, options, (path) =>\n direction === \"outgoing\"\n ? ((this.nodes\n .get(path)\n ?.imports.map((importEdge) => importEdge.toPath)\n .filter(Boolean) as string[]) ?? [])\n : (incoming?.get(path) ?? []),\n );\n }\n\n /**\n * @description Builds and caches a reverse index of call edges: target file path → list of\n * source file paths whose exported functions call into it. Computed lazily on first access\n * and reused for the lifetime of this Graph instance.\n * @returns {Map<string, string[]>} Map from target file path to list of source file paths that call into it.\n */\n private getCallIncomingCache(): Map<string, string[]> {\n if (this._callIncomingCache) return this._callIncomingCache;\n const cache = new Map<string, string[]>();\n for (const node of this.nodes.values()) {\n for (const edge of node.callEdges ?? []) {\n const list = cache.get(edge.toFile) ?? [];\n list.push(node.path);\n cache.set(edge.toFile, list);\n }\n }\n this._callIncomingCache = cache;\n return cache;\n }\n\n /**\n * @description Performs a DFS over call edges (exported-function → imported-symbol).\n * Outgoing follows callEdges forward; incoming follows the reverse call index.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Callback executed for each node; return `false` to stop traversing a branch.\n * @param options - Configuration for `maxDepth` and `direction`.\n */\n public traverseCalls(\n startPath: string,\n visitor: TraversalVisitor,\n options: TraversalOptions = {},\n ) {\n const direction = options.direction ?? \"outgoing\";\n const callIncoming = direction === \"incoming\" ? this.getCallIncomingCache() : null;\n this.dfs(startPath, visitor, options, (path) =>\n direction === \"outgoing\"\n ? (this.nodes.get(path)?.callEdges?.map((callEdge) => callEdge.toFile) ?? [])\n : (callIncoming?.get(path) ?? []),\n );\n }\n\n /**\n * @description Returns files whose exported functions call into the given file (one hop).\n * @param filePath - Project-relative path of the target file.\n * @returns Project-relative paths of all direct callers.\n */\n public getCallers(filePath: string): string[] {\n const callers: string[] = [];\n this.traverseCalls(\n filePath,\n (node) => {\n if (node.path !== filePath) callers.push(node.path);\n return true;\n },\n { direction: \"incoming\", maxDepth: 1 },\n );\n return callers;\n }\n\n /**\n * @description Returns all call edges originating from a file.\n * @param filePath - Project-relative path of the source file.\n * @returns The file's call edges, or an empty array if none exist.\n */\n public getCallEdgesFor(filePath: string): CallEdge[] {\n return this.nodes.get(filePath)?.callEdges ?? [];\n }\n\n /**\n * @description Returns the FileNodes that a given file directly imports —\n * the first-hop outgoing neighbours in the import graph.\n * @param path - Project-relative path of the node to look up.\n * @returns Array of FileNodes imported by the given file; empty if the path is unknown.\n */\n public getNeighbors(path: string): FileNode[] {\n const node = this.nodes.get(path);\n if (!node) return [];\n return node.imports\n .map((imp) => this.nodes.get(imp.toPath))\n .filter((node): node is FileNode => node !== undefined);\n }\n\n /**\n * @description Identifies files that are not reachable from any entry point\n * by walking the full import graph forward from each node.\n * @param allFiles - Complete list of project-relative file paths to test.\n * @returns Subset of `allFiles` that nothing imports, directly or transitively.\n */\n public findUnusedFiles(allFiles: string[]): string[] {\n return new GraphAnalyzer(this.nodes).findUnusedFiles(allFiles);\n }\n\n /**\n * @description Detects all circular import chains in the graph using DFS\n * with a back-edge check. Each returned array is one cycle as an ordered path.\n * @returns Array of cycles; each cycle is a list of file paths forming a loop.\n */\n public findCycles(): string[][] {\n return new GraphAnalyzer(this.nodes).findCycles();\n }\n}\n","/** Infers a coarse semantic role for a file node from its path and graph category. */\nimport type { FileNode } from \"../../types/node\";\nimport type { ModuleRole } from \"./types\";\n\n/**\n * Infers a coarse `ModuleRole` from a file's path and graph category.\n * Uses common directory-naming conventions so it works across any project layout.\n *\n * @param {FileNode} node - The file node to classify.\n * @returns {ModuleRole} The best-matching role, defaulting to `\"other\"`.\n */\nexport function inferRole(node: FileNode): ModuleRole {\n if (node.category === \"test\") return \"test\";\n if (node.category === \"config\") return \"config\";\n if (node.category === \"type-only\") return \"types\";\n\n const filePath = node.path;\n\n // Ordered most-specific → least-specific\n if (seg(filePath, \"component\") || seg(filePath, \"components\")) return \"component\";\n if (seg(filePath, \"controller\") || seg(filePath, \"controllers\")) return \"controller\";\n if (seg(filePath, \"middleware\")) return \"middleware\";\n if (seg(filePath, \"router\") || seg(filePath, \"routes\") || seg(filePath, \"route\")) return \"router\";\n if (seg(filePath, \"store\") || seg(filePath, \"stores\")) return \"store\";\n if (seg(filePath, \"service\") || seg(filePath, \"services\")) return \"service\";\n if (seg(filePath, \"handler\") || seg(filePath, \"handlers\")) return \"handler\";\n if (seg(filePath, \"adapter\") || seg(filePath, \"adapters\")) return \"adapter\";\n if (seg(filePath, \"plugin\") || seg(filePath, \"plugins\")) return \"plugin\";\n if (seg(filePath, \"api\")) return \"api\";\n if (seg(filePath, \"cli\") || seg(filePath, \"commands\") || fileBasename(filePath) === \"cli\")\n return \"cli\";\n if (\n seg(filePath, \"util\") ||\n seg(filePath, \"utils\") ||\n seg(filePath, \"helper\") ||\n seg(filePath, \"helpers\")\n )\n return \"util\";\n if (seg(filePath, \"model\") || seg(filePath, \"models\") || fileBasename(filePath) === \"model\")\n return \"model\";\n if (seg(filePath, \"parser\") || seg(filePath, \"parsers\") || fileBasename(filePath) === \"parser\")\n return \"parser\";\n if (fileBasename(filePath) === \"builder\") return \"builder\";\n if (fileBasename(filePath) === \"resolver\") return \"resolver\";\n\n return \"other\";\n}\n\n/**\n * Returns true when `segment` appears as a discrete path component.\n * Matches `/<segment>/` (directory) or `/<segment>.` (file) to avoid false\n * positives on names that merely contain the segment as a substring.\n *\n * @param {string} filePath - Project-relative file path to test.\n * @param {string} segment - Directory or filename stem to look for.\n * @returns {boolean} Whether `segment` is a standalone path component in `filePath`.\n */\nfunction seg(filePath: string, segment: string): boolean {\n return filePath.includes(`/${segment}/`) || filePath.includes(`/${segment}.`);\n}\n\n/**\n * Extracts the basename of a file path with its extension removed.\n *\n * @param {string} filePath - Project-relative file path (e.g. `src/graph/builder.ts`).\n * @returns {string} The stem of the filename (e.g. `builder`).\n */\nfunction fileBasename(filePath: string): string {\n const name = filePath.slice(filePath.lastIndexOf(\"/\") + 1);\n return name.slice(0, name.lastIndexOf(\".\")) || name;\n}\n","/** Builds a ResponsibilityGraph assigning each file a semantic role based on its connectivity and feature membership. */\nimport { buildFeatureGraph } from \"../features/feature-graph\";\nimport type { FeatureDetectionOptions } from \"../features/index\";\nimport type { Graph } from \"../model\";\nimport { inferRole } from \"./infer-role\";\nimport type { ResponsibilityGraph } from \"./types\";\n\nexport type { ModuleResponsibility, ModuleRole, ResponsibilityGraph } from \"./types\";\n\n/**\n * Builds a responsibility map for every file in the graph.\n *\n * Each entry is derived entirely from data already present in the `FileNode`:\n * - `description` comes from the file's leading JSDoc (`FileNode.description`)\n * - `exports` are the exported symbol names\n * - `role` is inferred from file path and category via `inferRole`\n * - `featureHub` is resolved via `buildFeatureGraph` with default options\n *\n * Test files are included with `role: \"test\"` so callers can filter them if needed.\n *\n * @param {Graph} graph - The import graph to derive responsibilities from.\n * @param {FeatureDetectionOptions} [featureOptions] - Options forwarded to `buildFeatureGraph` (e.g. `minOutDegree`).\n * @returns {ResponsibilityGraph} A map from each file path to its `ModuleResponsibility`.\n */\nexport function buildResponsibilityGraph(\n graph: Graph,\n featureOptions?: FeatureDetectionOptions,\n): ResponsibilityGraph {\n const featureGraph = buildFeatureGraph(graph, featureOptions);\n\n // Build a reverse map: file path → feature hub name.\n const fileToHub = new Map<string, string>();\n for (const [featureName, domain] of featureGraph.features) {\n for (const filePath of domain.files) {\n fileToHub.set(filePath, featureName);\n }\n // The hub itself belongs to its own feature.\n fileToHub.set(domain.hub, featureName);\n }\n\n const result: ResponsibilityGraph = new Map();\n for (const node of graph.nodes.values()) {\n const hub = fileToHub.get(node.path);\n result.set(node.path, {\n path: node.path,\n role: inferRole(node),\n ...(node.description ? { description: node.description } : {}),\n exports: node.exports.map((exportedSym) => exportedSym.name),\n ...(hub ? { featureHub: hub } : {}),\n });\n }\n\n return result;\n}\n","import type { ImportEdge } from \"../types/node\";\n\n/**\n * @description Tracks which exported symbols of each visited node are \"affected\" by a change.\n *\n * Enables symbol-level pruning during graph traversal: if a node only imports `foo` and `foo`\n * was not among the changed symbols, that node is not considered affected and traversal stops there.\n */\nexport class SymbolTraversalContext {\n private affectedSymbols = new Map<string, Set<string>>();\n\n /**\n * @param {string} startPath - Relative path of the changed file; seeded with the given affected symbols.\n * @param {string[]} affectedSymbols - Symbol names that are considered changed. Pass `[\"*\"]` to treat the whole file as changed.\n */\n constructor(startPath: string, affectedSymbols: string[]) {\n // Callers that want namespace-import consumers to always be affected should include \"*\" explicitly.\n this.affectedSymbols.set(startPath, new Set([\"default\", ...affectedSymbols]));\n }\n\n /**\n * @description Checks whether `visitedNode` imports any affected symbol from `childPath` and,\n * if so, propagates the affected symbol set to `visitedNode` for the next traversal step.\n *\n * Both roles live in one method to avoid a second pass over the import edges — the check\n * and the update read the same edge, so splitting them would duplicate work.\n * @param {{ path: string; imports: ImportEdge[] }} visitedNode - The node currently being evaluated; its imports are inspected.\n * @param {string} childPath - The path it was reached from; used to look up the current affected symbols.\n * @returns {boolean} `true` if at least one imported symbol is affected and traversal should continue; `false` to prune.\n */\n public updateAffectedSymbols(\n visitedNode: { path: string; imports: ImportEdge[] },\n childPath: string,\n ): boolean {\n const currentSymbols = this.affectedSymbols.get(childPath) || new Set();\n\n const importEdge = visitedNode.imports.find((imp) => imp.toPath === childPath);\n if (!importEdge) return false;\n\n const importedSymbols = importEdge.symbols || [\"*\"];\n const relevantSymbols = new Set<string>();\n\n for (const sym of importedSymbols) {\n if (sym === \"*\" || currentSymbols.has(\"*\") || currentSymbols.has(sym)) {\n relevantSymbols.add(\"*\");\n }\n }\n\n if (relevantSymbols.size === 0) return false;\n\n const existing = this.affectedSymbols.get(visitedNode.path) || new Set();\n for (const symbol of relevantSymbols) existing.add(symbol);\n this.affectedSymbols.set(visitedNode.path, existing);\n return true;\n }\n}\n","/** Builds and queries a TypeGraph of interface, class, enum, and type-alias exports and the files that reference them. */\nimport type { ExportedSymbol, FileNode } from \"../types/node\";\nimport type { Graph } from \"./model\";\n\n/** Structural kind of a type export. */\nexport type TypeKind = \"interface\" | \"class\" | \"enum\" | \"type\";\n\n/**\n * A single type-like export extracted from a TypeScript source file.\n * Only interfaces, classes, enums, and type aliases are included —\n * plain functions and values are excluded.\n */\nexport interface TypeNode {\n /** Exported symbol name (e.g. `\"FileNode\"`). */\n name: string;\n /** Project-relative path of the file that exports this type. */\n file: string;\n /** Structural kind inferred from the export signature. */\n kind: TypeKind;\n /** JSDoc description attached to the export, if present. */\n doc?: string;\n}\n\n/**\n * A directed edge representing that one file imports a specific type from another file.\n */\nexport interface TypeEdge {\n /** Project-relative path of the importing file. */\n fromFile: string;\n /** Name of the imported type. */\n toType: string;\n /** Project-relative path of the file that defines the type. */\n toFile: string;\n}\n\n/**\n * Type-level view of the import graph.\n * Answers \"what types depend on X?\" and \"what does type X depend on?\"\n * at a fraction of the cost of sending the full graph.\n */\nexport interface TypeGraph {\n /**\n * All type-like exports in the graph.\n * Key format: `\"<file>::<typeName>\"` (e.g. `\"src/types/node.ts::FileNode\"`).\n */\n types: Map<string, TypeNode>;\n /** All import edges where the imported symbol is a known type. */\n edges: TypeEdge[];\n}\n\n/**\n * Result of a focused query for one named type.\n * A token-efficient answer to \"what types depend on X?\" and \"what does X use?\"\n */\nexport interface TypeQueryResult {\n /** The type that was queried. */\n type: TypeNode | null;\n /** Project-relative paths of files that import this type. */\n usedByFiles: string[];\n /** Types that the defining file imports from other files. */\n uses: TypeNode[];\n}\n\n/**\n * Infers the `TypeKind` from an export's signature string.\n * Matches the prefix patterns produced by `extractSignature` in the TS parser.\n *\n * @param signature - The signature string from `ExportedSymbol.signature`, if present.\n * @returns The inferred `TypeKind`.\n */\nfunction inferKind(signature: string | undefined): TypeKind {\n if (!signature) return \"type\";\n if (signature.startsWith(\"interface \")) return \"interface\";\n if (signature.startsWith(\"class \")) return \"class\";\n if (signature.startsWith(\"enum \")) return \"enum\";\n return \"type\";\n}\n\n/**\n * Returns `true` when a symbol is a type-like export that should appear in the type graph.\n *\n * Structural types (interface / class / enum) are always included.\n * In `type-only` files every export is treated as a type.\n * Plain functions and values (signatures without a structural prefix) are excluded\n * unless they live in a `type-only` file.\n *\n * @param sym - The exported symbol to test.\n * @param category - The file's category from the import graph.\n * @returns `true` if the symbol should be a `TypeNode`.\n */\nfunction isTypeExport(sym: ExportedSymbol, category: FileNode[\"category\"]): boolean {\n if (category === \"type-only\") return true;\n const sig = sym.signature ?? \"\";\n return sig.startsWith(\"interface \") || sig.startsWith(\"class \") || sig.startsWith(\"enum \");\n}\n\n/**\n * Builds a type-level view of the import graph by extracting all type-like exports\n * and the import edges that connect them.\n *\n * Only TypeScript and JavaScript files are considered — other file types carry no\n * type information usable for this graph.\n *\n * @param graph - The import graph to derive the type graph from.\n * @returns A `TypeGraph` with all type nodes and their dependency edges.\n */\nexport function buildTypeGraph(graph: Graph): TypeGraph {\n const types = new Map<string, TypeNode>();\n\n // Pass 1: collect type nodes from all TS/JS files.\n for (const node of graph.nodes.values()) {\n if (node.type !== \"typescript\" && node.type !== \"javascript\") continue;\n for (const exp of node.exports) {\n if (!isTypeExport(exp, node.category)) continue;\n const key = `${node.path}::${exp.name}`;\n types.set(key, {\n name: exp.name,\n file: node.path,\n kind: inferKind(exp.signature),\n ...(exp.doc ? { doc: exp.doc } : {}),\n });\n }\n }\n\n // Pass 2: build edges from import edges whose symbols resolve to known types.\n const edges: TypeEdge[] = [];\n for (const node of graph.nodes.values()) {\n if (node.type !== \"typescript\" && node.type !== \"javascript\") continue;\n for (const imp of node.imports) {\n if (!imp.toPath || imp.isExternal || !imp.symbols?.length) continue;\n for (const sym of imp.symbols) {\n if (types.has(`${imp.toPath}::${sym}`)) {\n edges.push({ fromFile: node.path, toType: sym, toFile: imp.toPath });\n }\n }\n }\n }\n\n return { types, edges };\n}\n\n/**\n * Queries the type graph for a specific named type, returning its direct dependents\n * (files that import it) and its direct dependencies (types it imports).\n *\n * When `typeName` is not found in the graph, `type` is `null` and both lists are empty.\n *\n * @param typeGraph - A previously built `TypeGraph`.\n * @param typeName - Exact exported name of the type to look up (e.g. `\"FileNode\"`).\n * @returns `TypeQueryResult` with the type node and its one-hop neighbours.\n */\nexport function queryTypeGraph(typeGraph: TypeGraph, typeName: string): TypeQueryResult {\n // Find the canonical TypeNode for the given name (use the first match if there are multiple files).\n let target: TypeNode | null = null;\n for (const typeNode of typeGraph.types.values()) {\n if (typeNode.name === typeName) {\n target = typeNode;\n break;\n }\n }\n\n if (!target) return { type: null, usedByFiles: [], uses: [] };\n\n const usedByFiles = new Set<string>();\n const usesMap = new Map<string, TypeNode>();\n\n for (const edge of typeGraph.edges) {\n // Files that import this type.\n if (edge.toType === typeName && edge.toFile === target.file) {\n usedByFiles.add(edge.fromFile);\n }\n // Types that the defining file itself imports.\n if (edge.fromFile === target.file) {\n const dep = typeGraph.types.get(`${edge.toFile}::${edge.toType}`);\n if (dep) usesMap.set(`${dep.file}::${dep.name}`, dep);\n }\n }\n\n return {\n type: target,\n usedByFiles: Array.from(usedByFiles),\n uses: Array.from(usesMap.values()),\n };\n}\n","/** Runs all registered monorepo detectors and returns the layout describing the detected tool and packages. */\nimport path from \"node:path\";\nimport { npmDetector } from \"./detectors/npm\";\nimport { nxDetector } from \"./detectors/nx\";\nimport { pnpmDetector } from \"./detectors/pnpm\";\n\nimport { turborepoDetector } from \"./detectors/turborepo\";\nimport { yarnDetector } from \"./detectors/yarn\";\nimport type { MonorepoDetector } from \"./registry\";\nimport { getMonorepoDetectors, registerMonorepoDetector } from \"./registry\";\nimport type { MonorepoLayout, WorkspacePackage } from \"./types\";\n\n// Register in priority order: orchestration tools first, then package managers.\nregisterMonorepoDetector(turborepoDetector);\nregisterMonorepoDetector(nxDetector);\nregisterMonorepoDetector(pnpmDetector);\nregisterMonorepoDetector(yarnDetector);\nregisterMonorepoDetector(npmDetector);\n\n/**\n * @description Runs all registered monorepo detectors against `rootDir` and merges\n * their results into a single `MonorepoLayout`. All matching detectors contribute\n * their `type` string and packages — so a Turborepo + pnpm repo will have\n * `types: [\"turborepo\", \"pnpm\"]` and packages from the pnpm detector.\n *\n * Packages are deduplicated by name: the first detector to emit a package name wins.\n * Returns `type: \"none\"` when no detector fires.\n */\nexport function detectMonorepo(\n rootDir: string,\n detectors: readonly MonorepoDetector[] = getMonorepoDetectors(),\n): MonorepoLayout {\n const abs = path.resolve(rootDir);\n const allPackages = new Map<string, WorkspacePackage>();\n const detectedTypes: string[] = [];\n\n for (const detector of detectors) {\n const pkgs = detector.detect(abs);\n if (pkgs === null) continue;\n detectedTypes.push(detector.type);\n for (const pkg of pkgs) {\n if (!allPackages.has(pkg.name)) allPackages.set(pkg.name, pkg);\n }\n }\n\n if (detectedTypes.length === 0) {\n return { root: abs, type: \"none\", types: [], packages: [], packageMap: new Map() };\n }\n\n const packages = Array.from(allPackages.values());\n return {\n root: abs,\n type: detectedTypes[0] as string,\n types: detectedTypes,\n packages,\n packageMap: new Map(packages.map((pkg) => [pkg.name, pkg])),\n };\n}\n\nexport type { MonorepoDetector } from \"./registry\";\nexport { registerMonorepoDetector } from \"./registry\";\nexport type { MonorepoLayout, WorkspacePackage } from \"./types\";\n","/** Monorepo detector for npm workspaces (package.json workspaces field). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects npm workspaces via `package.json` `\"workspaces\"` field.\n * Yields to the yarn detector when `yarn.lock` is present in the same root.\n */\nexport const npmDetector: MonorepoDetector = {\n type: \"npm\",\n detect(rootDir) {\n const pkgPath = path.join(rootDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n let workspaces: string[] | { packages?: string[] } | undefined;\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: string[] | { packages?: string[] };\n };\n workspaces = pkg.workspaces;\n } catch {\n return null;\n }\n\n if (!workspaces) return null;\n\n // Skip if yarn.lock present — yarn detector handles that repo\n if (fs.existsSync(path.join(rootDir, \"yarn.lock\"))) return null;\n\n const patterns: string[] = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);\n\n if (patterns.length === 0) return null;\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Shared helpers for monorepo detectors: package building, entry-point resolution, and glob pattern expansion. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { isDirectory, isFile } from \"./fs-utils\";\nimport type { WorkspacePackage } from \"./types\";\n\nexport { exists } from \"./fs-utils\";\n\n/**\n * @description Reads `package.json` from `pkgRoot` and builds a `WorkspacePackage`.\n * Returns `null` if no `package.json` exists, cannot be parsed, or the `name` field is absent.\n * @param {string} monorepoRoot - Absolute path to the monorepo root, used to compute `relativeRoot`.\n * @param {string} pkgRoot - Absolute path to the package directory to read.\n * @returns {WorkspacePackage | null} The built package descriptor, or `null` on failure.\n */\nexport function buildPackage(monorepoRoot: string, pkgRoot: string): WorkspacePackage | null {\n const pkgJsonPath = path.join(pkgRoot, \"package.json\");\n if (!fs.existsSync(pkgJsonPath)) return null;\n\n let pkgJson: { name?: string; main?: string; exports?: unknown } = {};\n try {\n pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\")) as typeof pkgJson;\n } catch {\n return null;\n }\n\n const name = pkgJson.name;\n if (!name) return null;\n\n return {\n name,\n root: pkgRoot,\n relativeRoot: path.relative(monorepoRoot, pkgRoot),\n entryPoints: resolveEntryPoints(pkgRoot, pkgJson),\n };\n}\n\n/**\n * @description Derives entry point absolute paths from a package's `package.json`.\n * Tries `exports[\".\"]`, `main`, and common conventions (`src/index.ts`, etc.) in that order.\n * Returns the first existing file, or the first candidate as a fallback when nothing exists on disk.\n * @param {string} pkgRoot - Absolute path to the package directory.\n * @param {{ main?: string; exports?: unknown }} pkgJson - Parsed `package.json` object.\n * @returns {string[]} A single-element array containing the resolved entry point absolute path.\n */\nexport function resolveEntryPoints(\n pkgRoot: string,\n pkgJson: { main?: string; exports?: unknown },\n): string[] {\n const candidates: string[] = [];\n\n if (pkgJson.exports) {\n const exp = pkgJson.exports;\n if (typeof exp === \"string\") {\n candidates.push(path.join(pkgRoot, exp));\n } else if (typeof exp === \"object\" && exp !== null) {\n const dot = (exp as Record<string, unknown>)[\".\"];\n if (typeof dot === \"string\") {\n candidates.push(path.join(pkgRoot, dot));\n } else if (typeof dot === \"object\" && dot !== null) {\n const src =\n (dot as Record<string, unknown>).import ??\n (dot as Record<string, unknown>).require ??\n (dot as Record<string, unknown>).default;\n if (typeof src === \"string\") candidates.push(path.join(pkgRoot, src));\n }\n }\n }\n\n if (pkgJson.main) candidates.push(path.join(pkgRoot, pkgJson.main));\n\n for (const c of [\"src/index.ts\", \"src/index.tsx\", \"index.ts\", \"index.tsx\", \"index.js\"]) {\n candidates.push(path.join(pkgRoot, c));\n }\n\n const existing = candidates.filter(isFile);\n return existing.length > 0 ? existing.slice(0, 1) : candidates.slice(0, 1);\n}\n\n/**\n * @description Resolves workspace glob patterns (e.g. `packages/*`) to `WorkspacePackage` entries.\n * Supports `*` (single directory segment) and `**` (recursive). Non-glob patterns are treated as literal paths.\n * @param {string} root - Absolute monorepo root directory used as the base for all patterns.\n * @param {string[]} patterns - Glob patterns from `package.json` `\"workspaces\"` or `pnpm-workspace.yaml`.\n * @returns {WorkspacePackage[]} All resolved packages found under the matching directories.\n */\nexport function resolveGlobPatterns(root: string, patterns: string[]): WorkspacePackage[] {\n const packages: WorkspacePackage[] = [];\n const seen = new Set<string>();\n\n for (const pattern of patterns) {\n const normalised = pattern.replace(/\\/$/, \"\").replace(/^\\.\\//, \"\");\n resolvePattern(root, normalised, seen, packages);\n }\n\n return packages;\n}\n\n/**\n * @description Dispatches a single normalised glob pattern to the appropriate resolver\n * based on whether it contains no wildcard, a `**` recursive glob, or a `*` shallow glob.\n * @param {string} root - Absolute monorepo root used as the base for path resolution.\n * @param {string} pattern - A single normalised pattern (trailing slash and leading `./` already stripped).\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolvePattern(\n root: string,\n pattern: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n if (!pattern.includes(\"*\")) {\n resolveLiteralPattern(root, pattern, seen, packages);\n return;\n }\n\n const segments = pattern.split(\"/\");\n\n if (segments.includes(\"**\")) {\n resolveRecursivePattern(root, segments, seen, packages);\n } else {\n resolveShallowPattern(root, segments, seen, packages);\n }\n}\n\n/**\n * @description Resolves a pattern with no wildcards as a literal directory path relative to `root`.\n * Adds a `WorkspacePackage` if the directory exists and has not been visited before.\n * @param {string} root - Absolute monorepo root used to join the literal path and compute `relativeRoot`.\n * @param {string} pattern - A literal (non-glob) relative path, e.g. `\"packages/core\"`.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveLiteralPattern(\n root: string,\n pattern: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const abs = path.join(root, pattern);\n if (seen.has(abs) || !isDirectory(abs)) return;\n seen.add(abs);\n const pkg = buildPackage(root, abs);\n if (pkg) packages.push(pkg);\n}\n\n/**\n * @description Resolves a `**` glob by walking all subdirectories under the base segment recursively.\n * If the pattern starts with `**` itself the walk begins at `root`; otherwise at the first segment.\n * @param {string} root - Absolute monorepo root passed through to `walkRecursive` for `relativeRoot` computation.\n * @param {string[]} segments - Path segments of the pattern split on `/`, must contain `\"**\"`.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveRecursivePattern(\n root: string,\n segments: string[],\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const base = path.join(root, segments[0] === \"**\" ? \"\" : (segments[0] ?? \"\"));\n walkRecursive(root, base, seen, packages);\n}\n\n/**\n * @description Resolves a single-`*` glob by listing every immediate subdirectory of the base path.\n * The base is everything before the first segment containing `*`, e.g. `packages` for `packages/*`.\n * @param {string} root - Absolute monorepo root used to join path segments and compute `relativeRoot`.\n * @param {string[]} segments - Path segments of the pattern split on `/`, must contain a `*` (but not `**`).\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveShallowPattern(\n root: string,\n segments: string[],\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const starIdx = segments.findIndex((segment) => segment.includes(\"*\"));\n const base = path.join(root, ...segments.slice(0, starIdx));\n\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(base, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const abs = path.join(base, entry.name);\n if (seen.has(abs)) continue;\n seen.add(abs);\n const pkg = buildPackage(root, abs);\n if (pkg) packages.push(pkg);\n }\n}\n\n/**\n * @description Recursively walks `dir` looking for directories that contain a `package.json`,\n * building a `WorkspacePackage` for each. Skips `node_modules` and hidden directories.\n * @param {string} monorepoRoot - Absolute path to the monorepo root, used to compute `relativeRoot`.\n * @param {string} dir - The directory to walk in this recursion step.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction walkRecursive(\n monorepoRoot: string,\n dir: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n const abs = path.join(dir, entry.name);\n if (fs.existsSync(path.join(abs, \"package.json\")) && !seen.has(abs)) {\n seen.add(abs);\n const pkg = buildPackage(monorepoRoot, abs);\n if (pkg) packages.push(pkg);\n } else {\n walkRecursive(monorepoRoot, abs, seen, packages);\n }\n }\n}\n","/** Filesystem utilities for monorepo detectors: existence checks for files and directories. */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * @description Returns `true` when `name` exists inside `root`.\n * @param {string} root - Absolute directory path to search within.\n * @param {string} name - File or directory name to look for.\n * @returns {boolean} `true` if the entry exists, `false` otherwise.\n */\nexport function exists(root: string, name: string): boolean {\n return fs.existsSync(path.join(root, name));\n}\n\n/**\n * @description Returns `true` when `p` is an existing directory. Never throws.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a directory.\n */\nexport function isDirectory(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/**\n * @description Returns `true` when `p` is an existing regular file. Never throws.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a regular file.\n */\nexport function isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() === true;\n } catch {\n return false;\n }\n}\n","/** Monorepo detector for Nx workspaces (nx.json + project.json files). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { isFile } from \"../fs-utils\";\nimport type { MonorepoDetector } from \"../registry\";\nimport type { WorkspacePackage } from \"../types\";\n\n/**\n * @description Detects Nx workspaces by scanning for `project.json` files under `nx.json`.\n * Supports both package-based repos (with per-project `package.json`) and integrated repos\n * (no per-project `package.json` — name taken from `project.json`).\n */\nexport const nxDetector: MonorepoDetector = {\n type: \"nx\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"nx.json\"))) return null;\n\n const seen = new Set<string>();\n return walkForProjectJsonDirs(rootDir, rootDir, seen, 0)\n .map((pkgRoot) => buildNxPackage(rootDir, pkgRoot, path.join(pkgRoot, \"project.json\")))\n .filter((pkg): pkg is WorkspacePackage => pkg !== null);\n },\n};\n\n/**\n * @description Recursively walks `dir` up to 4 levels deep and returns absolute paths\n * of directories that contain a `project.json`. Skips `node_modules`, `.nx`, `dist`, and\n * hidden directories. Each directory is returned at most once via `seen`.\n * @param {string} rootDir - The monorepo root; unused in the recursion but kept for future use.\n * @param {string} dir - The directory to walk in this recursion step.\n * @param {Set<string>} seen - Set of already-returned absolute paths; updated in place.\n * @param {number} depth - Current recursion depth; returns early when greater than 4.\n * @returns {string[]} Absolute paths of all directories containing `project.json` under `dir`.\n */\nfunction walkForProjectJsonDirs(\n rootDir: string,\n dir: string,\n seen: Set<string>,\n depth: number,\n): string[] {\n if (depth > 4) return [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const found: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const name = entry.name;\n if (name.startsWith(\".\") || name === \"node_modules\" || name === \"dist\" || name === \".nx\")\n continue;\n const fullPath = path.join(dir, name);\n if (fs.existsSync(path.join(fullPath, \"project.json\")) && !seen.has(fullPath)) {\n seen.add(fullPath);\n found.push(fullPath);\n } else {\n found.push(...walkForProjectJsonDirs(rootDir, fullPath, seen, depth + 1));\n }\n }\n return found;\n}\n\ntype NxProjectJson = {\n name?: string;\n sourceRoot?: string;\n targets?: {\n build?: {\n options?: { main?: string; entryFile?: string };\n };\n };\n};\n\n/**\n * @description Builds a `WorkspacePackage` from an Nx `project.json`.\n * If a `package.json` is also present, its `name`, `main`, and `exports` fields take priority\n * over `project.json` — supporting both integrated and package-based Nx repos.\n * @param {string} monorepoRoot - Absolute monorepo root, used to compute `relativeRoot`.\n * @param {string} pkgRoot - Absolute path to the project directory.\n * @param {string} projJsonPath - Absolute path to the `project.json` file to read.\n * @returns {WorkspacePackage | null} The built package, or `null` when no usable name can be determined.\n */\nfunction buildNxPackage(\n monorepoRoot: string,\n pkgRoot: string,\n projJsonPath: string,\n): WorkspacePackage | null {\n let projJson: NxProjectJson = {};\n try {\n projJson = JSON.parse(fs.readFileSync(projJsonPath, \"utf-8\")) as NxProjectJson;\n } catch {\n return null;\n }\n\n let name = projJson.name;\n let pkgMain: string | undefined;\n let pkgExports: unknown;\n\n const pkgJsonPath = path.join(pkgRoot, \"package.json\");\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\")) as {\n name?: string;\n main?: string;\n exports?: unknown;\n };\n name = pkgJson.name ?? name;\n pkgMain = pkgJson.main;\n pkgExports = pkgJson.exports;\n } catch {\n /* use project.json values */\n }\n }\n\n if (!name) return null;\n\n return {\n name,\n root: pkgRoot,\n relativeRoot: path.relative(monorepoRoot, pkgRoot),\n entryPoints: resolveNxEntryPoints(pkgRoot, projJson, pkgMain, pkgExports),\n };\n}\n\n/**\n * @description Derives entry point paths for an Nx project in priority order:\n * `targets.build.options.main` → `package.json` exports/main → `sourceRoot/index.ts`\n * → common `src/index.ts` conventions. Returns the first existing file, or the first candidate\n * as a fallback when nothing exists on disk.\n * @param {string} pkgRoot - Absolute path to the project directory.\n * @param {NxProjectJson} projJson - Parsed `project.json` contents.\n * @param {string} [pkgMain] - The `main` field from `package.json`, if present.\n * @param {unknown} [pkgExports] - The `exports` field from `package.json`, if present.\n * @returns {string[]} A single-element array with the resolved entry point absolute path.\n */\nfunction resolveNxEntryPoints(\n pkgRoot: string,\n projJson: NxProjectJson,\n pkgMain?: string,\n pkgExports?: unknown,\n): string[] {\n const candidates: string[] = [];\n\n const buildMain =\n projJson.targets?.build?.options?.main ?? projJson.targets?.build?.options?.entryFile;\n if (buildMain) {\n const repoRootGuess = path.resolve(pkgRoot, \"../..\");\n candidates.push(path.resolve(repoRootGuess, buildMain));\n candidates.push(path.resolve(pkgRoot, buildMain));\n }\n\n if (pkgExports) {\n const exp = pkgExports;\n if (typeof exp === \"string\") candidates.push(path.join(pkgRoot, exp));\n else if (typeof exp === \"object\" && exp !== null) {\n const dot = (exp as Record<string, unknown>)[\".\"];\n if (typeof dot === \"string\") candidates.push(path.join(pkgRoot, dot));\n }\n }\n if (pkgMain) candidates.push(path.join(pkgRoot, pkgMain));\n\n if (projJson.sourceRoot) {\n const repoRootGuess = path.resolve(pkgRoot, \"../..\");\n const srcRoot = path.resolve(repoRootGuess, projJson.sourceRoot);\n candidates.push(path.join(srcRoot, \"index.ts\"), path.join(srcRoot, \"index.tsx\"));\n }\n\n for (const c of [\"src/index.ts\", \"src/index.tsx\", \"index.ts\", \"index.tsx\"]) {\n candidates.push(path.join(pkgRoot, c));\n }\n\n const existing = candidates.filter(isFile);\n return existing.length > 0 ? existing.slice(0, 1) : candidates.slice(0, 1);\n}\n","/** Monorepo detector for pnpm workspaces (pnpm-workspace.yaml). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects pnpm workspaces via `pnpm-workspace.yaml`.\n * Reads the `packages:` glob list and resolves each pattern to `WorkspacePackage` entries.\n */\nexport const pnpmDetector: MonorepoDetector = {\n type: \"pnpm\",\n detect(rootDir) {\n const yamlPath = path.join(rootDir, \"pnpm-workspace.yaml\");\n if (!fs.existsSync(yamlPath)) return null;\n\n let patterns: string[] = [];\n try {\n const parsed = yaml.load(fs.readFileSync(yamlPath, \"utf-8\")) as {\n packages?: string[];\n } | null;\n patterns = parsed?.packages ?? [];\n } catch {\n return null;\n }\n\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Monorepo detector for Turborepo (turbo.json), contributing the orchestrator type without enumerating packages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\n\n/**\n * @description Turborepo detector. Turborepo is an orchestration layer on top of an\n * existing package manager — it contributes its type to `MonorepoLayout.types` but\n * returns no packages itself. Packages are enumerated by the pnpm/yarn/npm detector\n * that fires alongside it.\n */\nexport const turborepoDetector: MonorepoDetector = {\n type: \"turborepo\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"turbo.json\"))) return null;\n // Signal presence without contributing packages — other detectors handle that.\n return [];\n },\n};\n","/** Monorepo detector for Yarn Classic/Berry workspaces (yarn.lock + workspaces field). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects Yarn Classic / Berry workspaces.\n * Requires both `yarn.lock` and a `package.json` `\"workspaces\"` field to fire.\n */\nexport const yarnDetector: MonorepoDetector = {\n type: \"yarn\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"yarn.lock\"))) return null;\n\n const pkgPath = path.join(rootDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n let workspaces: string[] | { packages?: string[] } | undefined;\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: string[] | { packages?: string[] };\n };\n workspaces = pkg.workspaces;\n } catch {\n return null;\n }\n\n if (!workspaces) return null;\n\n const patterns: string[] = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);\n\n if (patterns.length === 0) return null;\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Registry for MonorepoDetector plugins, allowing custom detectors to be added alongside the built-in ones. */\nimport type { WorkspacePackage } from \"./types\";\n\n/**\n * @description Contract for a tool-specific monorepo detector.\n * Each detector knows how to recognise one package manager or build orchestrator\n * and enumerate the packages it manages.\n */\nexport interface MonorepoDetector {\n /**\n * Identifier for this tool (e.g. `\"pnpm\"`, `\"nx\"`).\n * Included in `MonorepoLayout.types` when this detector fires.\n */\n readonly type: string;\n /**\n * @description Inspects `rootDir` and returns the workspace packages it manages.\n * Return `null` to signal \"this tool is not present here\" (detector does not fire).\n * Return an empty array to signal \"tool is present but manages no packages\" (detector fires, contributes its type).\n * @param {string} rootDir - Absolute path to the repository root to inspect.\n * @returns {WorkspacePackage[] | null} Discovered packages, an empty array if the tool is present but empty, or `null` if the tool is absent.\n */\n detect(rootDir: string): WorkspacePackage[] | null;\n}\n\nconst registry: MonorepoDetector[] = [];\n\n/**\n * @description Registers a monorepo detector. Detectors are run in registration order;\n * register higher-priority tools first (e.g. Turborepo before pnpm).\n * @param {MonorepoDetector} detector - The detector implementation to add to the registry.\n */\nexport function registerMonorepoDetector(detector: MonorepoDetector): void {\n registry.push(detector);\n}\n\n/**\n * @description Returns all registered detectors in registration order.\n * @returns {readonly MonorepoDetector[]} Detectors in the order they were registered.\n */\nexport function getMonorepoDetectors(): readonly MonorepoDetector[] {\n return registry;\n}\n","/** 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/** @description Matches `NodeQuery.path` as a substring of `FileNode.path`. */\nexport const matchPath: NodeMatcher = (node, query) =>\n !query.path || matchesPath(node.path, query.path);\n\n/** @description Matches `NodeQuery.isExternal` against whether the node has any external import. */\nexport const matchIsExternal: NodeMatcher = (node, query) => {\n if (query.isExternal === undefined) return true;\n const hasExternalImport = node.imports.some((importEdge) => importEdge.isExternal);\n return hasExternalImport === query.isExternal;\n};\n\n/**\n * @description Matches `NodeQuery.tags` using OR logic across positive entries; entries\n * prefixed with `!` act as mandatory exclusions evaluated independently of the positive set.\n */\nexport const matchTags: NodeMatcher = (node, query) => {\n if (!query.tags || query.tags.length === 0) return true;\n const positiveTags = query.tags.filter((tag) => !tag.startsWith(\"!\"));\n const negativeTags = query.tags.filter((tag) => tag.startsWith(\"!\")).map((tag) => tag.slice(1));\n if (\n positiveTags.length > 0 &&\n !positiveTags.some((tag) => node.tags.some((structuredTag) => structuredTag.name === tag))\n )\n return false;\n if (negativeTags.some((tag) => node.tags.some((structuredTag) => structuredTag.name === tag)))\n return false;\n return true;\n};\n\n/** @description Matches `NodeQuery.allTags` using AND logic — every entry must be present. */\nexport const matchAllTags: NodeMatcher = (node, query) =>\n !query.allTags?.length ||\n query.allTags.every((tag) => node.tags.some((structuredTag) => structuredTag.name === tag));\n\n/** @description Matches `NodeQuery.importsFile` as a substring of any import's `toPath`. */\nexport const matchImportsFile: NodeMatcher = (node, query) =>\n !query.importsFile ||\n node.imports.some((importEdge) => importEdge.toPath?.includes(query.importsFile as string));\n\n/** @description Matches `NodeQuery.importedBy` as a substring of any importer path in `reverseIndex`. */\nexport const matchImportedBy: NodeMatcher = (node, query, reverseIndex) => {\n if (query.importedBy === undefined) return true;\n const importerPaths = reverseIndex?.get(node.path) ?? [];\n return importerPaths.some((importerPath) => importerPath.includes(query.importedBy as string));\n};\n\n/** @description Matches `NodeQuery.minImports` — node's import count must be at least this value. */\nexport const matchMinImports: NodeMatcher = (node, query) =>\n query.minImports === undefined || node.imports.length >= query.minImports;\n\n/** @description Matches `NodeQuery.maxImports` — node's import count must be at most this value. */\nexport const matchMaxImports: NodeMatcher = (node, query) =>\n query.maxImports === undefined || node.imports.length <= query.maxImports;\n\n/** @description Matches `NodeQuery.minSize` — node's file size must be at least this value. */\nexport const matchMinSize: NodeMatcher = (node, query) =>\n query.minSize === undefined || node.size >= query.minSize;\n\n/** @description Matches `NodeQuery.maxSize` — node's file size must be at most this value. */\nexport const matchMaxSize: NodeMatcher = (node, query) =>\n query.maxSize === undefined || node.size <= query.maxSize;\n\n/** @description Matches `NodeQuery.hasDocstring` against whether `FileNode.description` is set. */\nexport const matchHasDocstring: NodeMatcher = (node, query) =>\n query.hasDocstring === undefined || !!node.description === query.hasDocstring;\n\n/**\n * @description Matches `NodeQuery.minCoverage`. Nodes with no coverage data are excluded\n * (treated as 101%, i.e. always above any real threshold) — matching the\n * \"uncovered by default\" convention.\n */\nexport const matchMinCoverage: NodeMatcher = (node, query) =>\n query.minCoverage === undefined || (node.coveragePct ?? 101) >= query.minCoverage;\n\n/**\n * @description Matches `NodeQuery.maxCoverage`. Nodes with no coverage data are included\n * (treated as 0%) — matching the \"uncovered by default\" convention.\n */\nexport const matchMaxCoverage: NodeMatcher = (node, query) =>\n query.maxCoverage === undefined || (node.coveragePct ?? 0) <= query.maxCoverage;\n\n/** @description Matches `NodeQuery.minExportUsage`. Nodes with no coupling data are excluded. */\nexport const matchMinExportUsage: NodeMatcher = (node, query) =>\n query.minExportUsage === undefined || (node.avgExportUsage ?? -1) >= query.minExportUsage;\n\n/** @description Matches `NodeQuery.maxExportUsage`. Nodes with no coupling data are included (treated as 0). */\nexport const matchMaxExportUsage: NodeMatcher = (node, query) =>\n query.maxExportUsage === undefined || (node.avgExportUsage ?? 0) <= query.maxExportUsage;\n\n/** @description All matchers, applied in order by `matchNode`. Add new filter keys here. */\nexport const NODE_MATCHERS: NodeMatcher[] = [\n matchCategory,\n matchType,\n matchPath,\n matchIsExternal,\n matchTags,\n matchAllTags,\n matchImportsFile,\n matchImportedBy,\n matchMinImports,\n matchMaxImports,\n matchMinSize,\n matchMaxSize,\n matchHasDocstring,\n matchMinCoverage,\n matchMaxCoverage,\n matchMinExportUsage,\n matchMaxExportUsage,\n];\n","/** Filters a graph by applying NodeQuery predicates: category, type, tag, path, imports, coverage, and more. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport type { FileNode } from \"../types/node\";\nimport { NODE_MATCHERS } from \"./matchers\";\nimport type { NodeQuery } from \"./types\";\n\n/**\n * @description Tests whether a graph node satisfies all criteria in `query` by running it\n * through every matcher in `NODE_MATCHERS`. String fields use exact match with an optional\n * `!` prefix for negation. `tags` uses OR logic across positive entries; negated tags act\n * as mandatory exclusions. Adding a new query key requires adding a new matcher to\n * `NODE_MATCHERS`, not editing this function.\n * @param {FileNode} node - The graph node to evaluate.\n * @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.\n * @param {Map<string, string[]>} reverseIndex - Optional reverse importer lookup, required when `query.importedBy` is set.\n * @returns {boolean} `true` if the node passes every active filter criterion.\n */\nexport function matchNode(\n node: FileNode,\n query: NodeQuery,\n reverseIndex?: Map<string, string[]>,\n): boolean {\n return NODE_MATCHERS.every((matcher) => matcher(node, query, reverseIndex));\n}\n\n/**\n * @description Filters a serialized graph to only nodes matching all criteria in `query`,\n * then trims each node's import list to edges whose target is also in the result set.\n * Optionally sorts the result and applies a `limit`.\n * @param {SerializedGraph} graph - The serialized graph to filter.\n * @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.\n * @returns {SerializedGraph} A new `SerializedGraph` containing only the matching subgraph.\n */\nexport function filterGraph(graph: SerializedGraph, query: NodeQuery): SerializedGraph {\n const reverseIndex = new Map<string, string[]>();\n if (query.importedBy !== undefined) {\n for (const node of graph.nodes) {\n for (const imp of node.imports) {\n if (imp.toPath) {\n const arr = reverseIndex.get(imp.toPath) ?? [];\n arr.push(node.path);\n reverseIndex.set(imp.toPath, arr);\n }\n }\n }\n }\n\n const filteredNodes = graph.nodes.filter((node) => matchNode(node, query, reverseIndex));\n const nodePaths = new Set(filteredNodes.map((node) => node.path));\n\n const resultNodes = filteredNodes.map((node) => ({\n ...node,\n imports: node.imports.filter((imp) => !imp.toPath || nodePaths.has(imp.toPath)),\n }));\n\n if (query.sort) {\n resultNodes.sort((nodeA, nodeB) => {\n if (query.sort === \"size\") return nodeB.size - nodeA.size;\n if (query.sort === \"imports\") return nodeB.imports.length - nodeA.imports.length;\n if (query.sort === \"commitCount90d\")\n return (nodeB.commitCount90d ?? 0) - (nodeA.commitCount90d ?? 0);\n if (query.sort === \"exportUsage\")\n return (nodeB.avgExportUsage ?? 0) - (nodeA.avgExportUsage ?? 0);\n return 0;\n });\n }\n if (query.limit !== undefined) resultNodes.splice(query.limit);\n\n return {\n nodes: resultNodes,\n cycles:\n graph.cycles?.filter((cycle) => cycle.every((path) => nodePaths.has(path))) ?? undefined,\n };\n}\n","/** Parses a key:value query string into a structured NodeQuery for use with filterGraph. */\nimport type { NodeQuery } from \"./types\";\n\n/**\n * @description Parses a `\"key:value,key:value\"` query string into a structured `NodeQuery`.\n * String values support `\"!\"` prefix for negation. The `tag`/`tags` key may appear multiple\n * times; values are OR-matched (negated entries act as exclusions). `tag:a+b` maps to `allTags`.\n * @param {string} queryString - Comma-separated `key:value` pairs, e.g. `\"category:logic,tag:auth\"`.\n * @returns {NodeQuery} The structured query object ready for use with `filterGraph` or `matchNode`.\n */\nexport function parseQuery(queryString: string): NodeQuery {\n const query: NodeQuery = {};\n const parts = queryString.split(\",\");\n\n for (const part of parts) {\n const colonIdx = part.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = part.slice(0, colonIdx).trim().toLowerCase();\n const value = part.slice(colonIdx + 1).trim();\n if (!key || !value) continue;\n\n switch (key) {\n case \"category\":\n query.category = value;\n break;\n case \"type\":\n query.type = value;\n break;\n case \"tag\":\n case \"tags\":\n if (value.includes(\"+\")) {\n query.allTags = [...(query.allTags ?? []), ...value.split(\"+\")];\n } else {\n query.tags = [...(query.tags ?? []), value];\n }\n break;\n case \"path\":\n query.path = value;\n break;\n case \"external\":\n query.isExternal = value.toLowerCase() === \"true\";\n break;\n case \"importsfile\":\n query.importsFile = value;\n break;\n case \"importedby\":\n query.importedBy = value;\n break;\n case \"minimports\":\n query.minImports = parseInt(value, 10);\n break;\n case \"maximports\":\n query.maxImports = parseInt(value, 10);\n break;\n case \"minsize\":\n query.minSize = parseInt(value, 10);\n break;\n case \"maxsize\":\n query.maxSize = parseInt(value, 10);\n break;\n case \"sort\":\n query.sort = value as \"size\" | \"imports\" | \"commitCount90d\" | \"exportUsage\";\n break;\n case \"limit\":\n query.limit = parseInt(value, 10);\n break;\n case \"hasdocstring\":\n query.hasDocstring = value.toLowerCase() !== \"false\";\n break;\n case \"mincoverage\":\n query.minCoverage = parseInt(value, 10);\n break;\n case \"maxcoverage\":\n query.maxCoverage = parseInt(value, 10);\n break;\n case \"minexportusage\":\n query.minExportUsage = parseFloat(value);\n break;\n case \"maxexportusage\":\n query.maxExportUsage = parseFloat(value);\n break;\n }\n }\n\n return query;\n}\n","/** Writes tag annotations into test files using a framework-specific strategy. */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { loadMokoshConfig } from \"../config\";\nimport type { Graph } from \"../graph\";\nimport { createStrategies, getStrategyForFile, type TagApplierStrategy } from \"./strategies\";\n\n// Valid tag names must be simple identifiers; colons (node:fs), slashes, and @ sigils are excluded.\nconst VALID_TAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]{1,}$/;\n\n// Only filename-derived import-kind tags qualify for writing. comment-marker tags are excluded\n// because collectStringLiteralAtTags extracts @word from all string literals, including external\n// package names in import paths (e.g. \"@modelcontextprotocol/sdk\" → tag \"modelcontextprotocol\").\nconst ALLOWED_TAG_KINDS = new Set([\"import\"]);\n\n// Generic structural names that appear in nearly every project and carry no domain signal.\nconst GENERIC_TAG_BLOCKLIST = new Set([\n \"common\",\n \"fixture\",\n \"fixtures\",\n \"helper\",\n \"helpers\",\n \"index\",\n \"main\",\n \"mock\",\n \"mocks\",\n \"setup\",\n \"shared\",\n \"spec\",\n \"test\",\n \"tests\",\n \"types\",\n \"util\",\n \"utils\",\n]);\n\n/**\n * @description Result for a single file processed by {@link applyTagsToFile}.\n */\nexport interface ApplyTagsFileResult {\n /** Project-relative path of the test file. */\n path: string;\n /** `\"updated\"` when the file was rewritten, `\"unchanged\"` when tags already matched, `\"error\"` on I/O failure. */\n status: \"updated\" | \"unchanged\" | \"error\";\n /** Present only when status is `\"error\"`. */\n error?: string;\n}\n\n/**\n * @description Aggregate result returned by {@link applyTags} after processing all test nodes.\n */\nexport interface ApplyTagsResult {\n /** Number of files that were written (or would have been written in dry-run mode). */\n updated: number;\n /** Number of files where the existing tags already matched the computed tags. */\n unchanged: number;\n /** Number of files that could not be read or written. */\n errors: number;\n /** Per-file breakdown. */\n files: ApplyTagsFileResult[];\n}\n\n/**\n * @description Reads a single test file, delegates tag injection to the appropriate strategy,\n * and writes the result back to disk (unless `dryRun` is true).\n * @param {string} absPath - Absolute path of the test file to update.\n * @param {string[]} tags - Computed tag names (filtered, sorted) to write.\n * @param {boolean} dryRun - When true, computes the change but skips the `fs.writeFile` call.\n * @param {TagApplierStrategy[]} strategies - Ordered strategy list; first matching strategy wins.\n * @returns {Promise<ApplyTagsFileResult>} Result object with path and status.\n */\nexport async function applyTagsToFile(\n absPath: string,\n tags: string[],\n dryRun: boolean,\n strategies: TagApplierStrategy[],\n): Promise<ApplyTagsFileResult> {\n let original: string;\n try {\n original = await fs.readFile(absPath, \"utf8\");\n } catch (err) {\n return { path: absPath, status: \"error\", error: String(err) };\n }\n\n const strategy = getStrategyForFile(absPath, strategies);\n if (!strategy) return { path: absPath, status: \"unchanged\" };\n\n const newContent = strategy.apply(absPath, original, tags);\n if (newContent === original) return { path: absPath, status: \"unchanged\" };\n\n if (!dryRun) await fs.writeFile(absPath, newContent, \"utf8\");\n return { path: absPath, status: \"updated\" };\n}\n\n/**\n * @description Iterates every test node in the graph, extracts `\"import\"` kind tags that pass\n * a name validity check and generic-name blocklist, then delegates writing to the strategy\n * selected by `mokosh.config.*` (`tagApplier.framework`, default `\"vitest\"`). Non-test nodes\n * are skipped.\n * @param {Graph} graph - The fully-enriched dependency graph.\n * @param {string} rootDir - Absolute path to the project root.\n * @param {{ dryRun: boolean }} options - Pass `dryRun: true` to preview changes without disk writes.\n * @returns {Promise<ApplyTagsResult>} Aggregate result with per-file status breakdown.\n */\nexport async function applyTags(\n graph: Graph,\n rootDir: string,\n options: { dryRun: boolean },\n): Promise<ApplyTagsResult> {\n const config = loadMokoshConfig(rootDir);\n const framework = config.tagApplier?.framework ?? \"vitest\";\n const frameworkOverrides = config.tagApplier?.frameworkOverrides ?? {};\n const strategies = createStrategies(framework, frameworkOverrides, rootDir);\n\n const result: ApplyTagsResult = { updated: 0, unchanged: 0, errors: 0, files: [] };\n\n for (const node of graph.nodes.values()) {\n if (node.category !== \"test\") continue;\n\n const seen = new Set<string>();\n const tagNames: string[] = [];\n for (const tag of node.tags) {\n if (!ALLOWED_TAG_KINDS.has(tag.kind)) continue;\n if (!VALID_TAG_NAME_RE.test(tag.name)) continue;\n if (GENERIC_TAG_BLOCKLIST.has(tag.name.toLowerCase())) continue;\n if (!seen.has(tag.name)) {\n seen.add(tag.name);\n tagNames.push(tag.name);\n }\n }\n tagNames.sort();\n\n const absPath = path.resolve(rootDir, node.path);\n const fileResult = await applyTagsToFile(absPath, tagNames, options.dryRun, strategies);\n fileResult.path = node.path;\n result.files.push(fileResult);\n if (fileResult.status === \"updated\") result.updated++;\n else if (fileResult.status === \"unchanged\") result.unchanged++;\n else result.errors++;\n }\n\n return result;\n}\n","/**\n * Strategy registry.\n *\n * Two categories of strategy:\n * Language strategies — auto-selected by file extension, always active regardless of config:\n * Gherkin (.feature), Pytest (.py), Go (*_test.go)\n * Framework strategies — selected per file by import-specifier detection for TS/JS files\n * where multiple frameworks are common: Vitest, Playwright, Cypress,\n * Jest. A repo mixing frameworks (e.g. Jest for unit tests, Playwright\n * for e2e) gets each file tagged in its own framework's native format.\n *\n * Lookup order: language strategies checked first (narrow extension predicates), the\n * auto-detecting framework strategy checked last for any TS/JS file the language strategies\n * don't claim.\n */\n\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport { CypressStrategy } from \"./cypress\";\nimport { GherkinStrategy } from \"./gherkin\";\nimport { matchesGlob } from \"./glob\";\nimport { GoStrategy } from \"./go\";\nimport { JestStrategy } from \"./jest\";\nimport { PlaywrightStrategy } from \"./playwright\";\nimport { PytestStrategy } from \"./pytest\";\nimport { TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy, TagFramework } from \"./types\";\nimport { VitestStrategy } from \"./vitest\";\n\nexport type { TagApplierStrategy, TagFramework };\n\nconst FRAMEWORK_STRATEGIES: Record<TagFramework, () => TagApplierStrategy> = {\n vitest: () => new VitestStrategy(),\n playwright: () => new PlaywrightStrategy(),\n cypress: () => new CypressStrategy(),\n jest: () => new JestStrategy(),\n};\n\n// Import specifiers that unambiguously identify which test framework a file uses.\nconst FRAMEWORK_IMPORT_MARKERS: Record<string, TagFramework> = {\n \"@playwright/test\": \"playwright\",\n cypress: \"cypress\",\n \"@jest/globals\": \"jest\",\n vitest: \"vitest\",\n};\n\n/**\n * @description Inspects a TS/JS file's top-level import declarations and returns the test\n * framework they identify, or null when no known framework import is present (e.g. a file\n * relying on Vitest/Jest `globals: true` with no explicit import).\n * @param {string} source - File source text.\n * @returns {TagFramework | null} The detected framework, or null if undetermined.\n */\nexport function detectFrameworkFromImports(source: string): TagFramework | null {\n const sf = ts.createSourceFile(\"detect.ts\", source, ts.ScriptTarget.Latest, true);\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;\n const framework = FRAMEWORK_IMPORT_MARKERS[stmt.moduleSpecifier.text];\n if (framework) return framework;\n }\n return null;\n}\n\n/**\n * @description Composite strategy for TS/JS files: detects the test framework from each file's\n * own imports and delegates to that framework's strategy. When detection is inconclusive\n * (e.g. `globals: true` with no explicit import), falls back to the first `frameworkOverrides`\n * glob pattern (checked in config order) that matches the file's project-relative path, then\n * to the scalar `defaultFramework`. This lets a single repo mix Jest/Vitest/Playwright/Cypress\n * test files and have each tagged in its native format.\n */\nclass AutoFrameworkStrategy implements TagApplierStrategy {\n readonly name = \"auto\";\n\n constructor(\n private readonly rootDir: string,\n private readonly defaultFramework: TagFramework,\n private readonly frameworkOverrides: [pattern: string, framework: TagFramework][],\n ) {}\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const framework =\n detectFrameworkFromImports(source) ?? this.matchOverride(absPath) ?? this.defaultFramework;\n const strategy = (FRAMEWORK_STRATEGIES[framework] ?? FRAMEWORK_STRATEGIES.vitest)();\n return strategy.apply(absPath, source, tags);\n }\n\n private matchOverride(absPath: string): TagFramework | null {\n const relPath = path.relative(this.rootDir, absPath).split(path.sep).join(\"/\");\n for (const [pattern, framework] of this.frameworkOverrides) {\n if (matchesGlob(pattern, relPath)) return framework;\n }\n return null;\n }\n}\n\n/**\n * @description Returns the ordered list of strategies to use for tag annotation.\n * Language strategies (Gherkin, Pytest, Go) are always included and checked first.\n * The auto-detecting framework strategy is appended last and handles TS/JS files.\n * @param {TagFramework} defaultFramework - Fallback TS/JS test framework used only when a file\n * has no detectable framework import and no matching `frameworkOverrides` pattern. Defaults to\n * `\"vitest\"`.\n * @param {Record<string, TagFramework>} frameworkOverrides - Path-glob pattern (project-relative)\n * to fallback framework. Checked in object key order; the first pattern that matches a file's\n * path wins. Only consulted when the file's own imports don't reveal a framework.\n * @param {string} rootDir - Absolute project root, used to compute each file's project-relative\n * path for matching against `frameworkOverrides` patterns.\n * @returns {TagApplierStrategy[]} Strategies in priority order.\n */\nexport function createStrategies(\n defaultFramework: TagFramework = \"vitest\",\n frameworkOverrides: Record<string, TagFramework> = {},\n rootDir: string = process.cwd(),\n): TagApplierStrategy[] {\n return [\n new GherkinStrategy(), // .feature\n new PytestStrategy(), // .py\n new GoStrategy(), // *_test.go\n new AutoFrameworkStrategy(rootDir, defaultFramework, Object.entries(frameworkOverrides)), // TS/JS, framework detected per file\n ];\n}\n\n/**\n * @description Finds the first strategy in the list that declares it can handle the file.\n * @param {string} absPath - Absolute file path.\n * @param {TagApplierStrategy[]} strategies - Ordered candidate strategies.\n * @returns {TagApplierStrategy | null} The matched strategy, or null if none applies.\n */\nexport function getStrategyForFile(\n absPath: string,\n strategies: TagApplierStrategy[],\n): TagApplierStrategy | null {\n return strategies.find((strategy) => strategy.canHandle(absPath)) ?? null;\n}\n","/**\n * Tag applier strategy for Cypress with @cypress/grep: injects { tags: ['@tag'] } into\n * describe/it/context calls.\n *\n * Requires: `npm install --save-dev @cypress/grep`\n * Setup: add `require('@cypress/grep/src/support')()` in cypress/support/e2e.ts\n * Filter at CI time with: `cypress run --env grepTags=@tagname`\n *\n * @see https://github.com/cypress-io/cypress/tree/develop/npm/grep\n */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nfunction toCypressLiteral(tags: string[]): string {\n // @cypress/grep convention: prefix each tag with '@'\n return toArrayLiteral(tags.map((tag) => `@${tag}`));\n}\n\nfunction normaliseExisting(raw: string[]): string[] {\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\nexport class CypressStrategy implements TagApplierStrategy {\n readonly name = \"cypress\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return source;\n\n const rawExisting = readArrayProp(calls[0]!, \"tags\", sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(normaliseExisting(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tags\", sf)\n : buildInjectReplacement(call, \"tags\", toCypressLiteral(sortedTags), sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\n }\n}\n","/** Shared TypeScript AST helpers for framework-specific tag injection strategies. */\nimport ts from \"typescript\";\n\nexport interface Replacement {\n start: number;\n end: number;\n text: string;\n}\n\nconst ANNOTATABLE_NAMES = new Set([\"describe\", \"test\", \"it\"]);\n\n/** Returns top-level describe/test/it call expressions from a parsed source file. */\nexport function findTopLevelCalls(sourceFile: ts.SourceFile): ts.CallExpression[] {\n const calls: ts.CallExpression[] = [];\n for (const stmt of sourceFile.statements) {\n if (!ts.isExpressionStatement(stmt)) continue;\n const expr = stmt.expression;\n if (!ts.isCallExpression(expr)) continue;\n const callee = expr.expression;\n if (ts.isIdentifier(callee) && ANNOTATABLE_NAMES.has(callee.text)) {\n calls.push(expr);\n }\n // Also handle property-access forms: test.describe, test.skip, etc.\n if (\n ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.expression) &&\n ANNOTATABLE_NAMES.has(callee.expression.text)\n ) {\n calls.push(expr);\n }\n }\n return calls;\n}\n\n/**\n * @description Reads an options-object argument from a call expression and extracts\n * the value of a named array property (e.g. `tags` or `tag`).\n * @param {ts.CallExpression} call - The call expression to inspect.\n * @param {string} propName - Name of the property to read from the options object.\n * @param {ts.SourceFile} sf - Source file needed for position information.\n * @returns {string[] | null} The array contents, or null if the property is not found.\n */\nexport function readArrayProp(\n call: ts.CallExpression,\n propName: string,\n sf: ts.SourceFile,\n): string[] | null {\n void sf; // used by callers for getStart/getEnd, not needed here\n for (const arg of call.arguments) {\n if (!ts.isObjectLiteralExpression(arg)) continue;\n const prop = arg.properties.find(\n (candidate): candidate is ts.PropertyAssignment =>\n ts.isPropertyAssignment(candidate) &&\n ts.isIdentifier(candidate.name) &&\n candidate.name.text === propName,\n );\n if (!prop || !ts.isArrayLiteralExpression(prop.initializer)) continue;\n return prop.initializer.elements.filter(ts.isStringLiteral).map((element) => element.text);\n }\n return null;\n}\n\n/**\n * @description Builds the source replacement needed to write an array property (e.g.\n * `tags` or `tag`) into a single call expression. Handles three cases:\n * 1. No options arg yet — inserts `{ <prop>: <value> }, ` before the last argument.\n * 2. Options object exists with the property — replaces the array in-place.\n * 3. Options object exists without the property — appends it before the closing brace.\n * @param {ts.CallExpression} call - The call expression to modify.\n * @param {string} propName - Name of the options property to inject (e.g. `\"tags\"` or `\"tag\"`).\n * @param {string} tagsLiteral - The serialised array literal to write (e.g. `'[\"a\", \"b\"]'`).\n * @param {ts.SourceFile} sf - Source file for position resolution.\n * @returns {Replacement | null} The replacement descriptor, or null when the call has no arguments.\n */\nexport function buildInjectReplacement(\n call: ts.CallExpression,\n propName: string,\n tagsLiteral: string,\n sf: ts.SourceFile,\n): Replacement | null {\n if (call.arguments.length === 0) return null;\n\n for (let i = 1; i < call.arguments.length; i++) {\n const arg = call.arguments[i]!;\n if (!ts.isObjectLiteralExpression(arg)) continue;\n\n const existingProp = arg.properties.find(\n (prop): prop is ts.PropertyAssignment =>\n ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName,\n );\n\n if (existingProp) {\n return {\n start: existingProp.initializer.getStart(sf),\n end: existingProp.initializer.getEnd(),\n text: tagsLiteral,\n };\n }\n\n const closeBrace = arg.getEnd() - 1;\n return {\n start: closeBrace,\n end: closeBrace,\n text: `${arg.properties.length > 0 ? \", \" : \"\"}${propName}: ${tagsLiteral}`,\n };\n }\n\n // No options object — insert before the callback (last argument)\n const callback = call.arguments[call.arguments.length - 1]!;\n return {\n start: callback.getStart(sf),\n end: callback.getStart(sf),\n text: `{ ${propName}: ${tagsLiteral} }, `,\n };\n}\n\n/**\n * @description Builds the replacement to remove a previously injected options property.\n * When the options object has only the target property, the whole options arg is removed.\n * When it has other properties, only the target property is removed.\n * @param {ts.CallExpression} call - The call expression to modify.\n * @param {string} propName - Name of the property to remove.\n * @param {ts.SourceFile} sf - Source file for position resolution.\n * @returns {Replacement | null} The replacement descriptor, or null when nothing to remove.\n */\nexport function buildRemoveReplacement(\n call: ts.CallExpression,\n propName: string,\n sf: ts.SourceFile,\n): Replacement | null {\n const args = call.arguments;\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (!ts.isObjectLiteralExpression(arg)) continue;\n\n const idx = arg.properties.findIndex(\n (prop): prop is ts.PropertyAssignment =>\n ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName,\n );\n if (idx < 0) continue;\n\n if (arg.properties.length === 1) {\n // Remove the entire options argument including the preceding `, `\n return { start: args[i - 1]!.getEnd(), end: arg.getEnd(), text: \"\" };\n }\n\n const prop = arg.properties[idx]!;\n if (idx === arg.properties.length - 1) {\n // Last property — also remove the preceding comma\n return { start: arg.properties[idx - 1]!.getEnd(), end: prop.getEnd(), text: \"\" };\n }\n // Not last — remove the property and the following separator\n return { start: prop.getStart(sf), end: arg.properties[idx + 1]!.getStart(sf), text: \"\" };\n }\n return null;\n}\n\n/** Applies a list of replacements to a source string in reverse-position order. */\nexport function applyReplacements(source: string, replacements: Replacement[]): string {\n const sorted = [...replacements].sort((left, right) => right.start - left.start);\n let result = source;\n for (const replacement of sorted) {\n result = result.slice(0, replacement.start) + replacement.text + result.slice(replacement.end);\n }\n return result;\n}\n\n/** Serialises a list of string tag names to an inline array literal: `[\"a\", \"b\"]`. */\nexport function toArrayLiteral(tags: string[]): string {\n return `[${tags.map((tag) => JSON.stringify(tag)).join(\", \")}]`;\n}\n\nexport const TS_EXTENSIONS = new Set([\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n]);\n","/**\n * Tag applier strategy for Gherkin .feature files: writes a `# <mokosh-tags>` comment block\n * with native `@tagname` lines before the Feature: declaration.\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst BLOCK_REGEX = /# <mokosh-tags>[\\s\\S]*?# <\\/mokosh-tags>\\n*/;\nconst EXISTING_TAG_REGEX = /^@([a-zA-Z0-9_-]+)/gm;\n\nfunction buildBlock(tags: string[]): string {\n return (\n [\"# <mokosh-tags>\", ...tags.map((tag) => `@${tag}`), \"# </mokosh-tags>\"].join(\"\\n\") + \"\\n\\n\"\n );\n}\n\nfunction readManualTags(content: string): Set<string> {\n const found = new Set<string>();\n EXISTING_TAG_REGEX.lastIndex = 0;\n let match = EXISTING_TAG_REGEX.exec(content);\n while (match !== null) {\n if (match[1]) found.add(match[1]);\n match = EXISTING_TAG_REGEX.exec(content);\n }\n return found;\n}\n\nexport class GherkinStrategy implements TagApplierStrategy {\n readonly name = \"gherkin\";\n\n canHandle(absPath: string): boolean {\n return path.extname(absPath).toLowerCase() === \".feature\";\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const manualContent = source.replace(BLOCK_REGEX, \"\");\n const manualTags = readManualTags(manualContent);\n const netNewTags = tags.filter((tag) => !manualTags.has(tag));\n\n const newBlock = netNewTags.length > 0 ? buildBlock(netNewTags) : \"\";\n\n if (BLOCK_REGEX.test(source)) {\n return source.replace(BLOCK_REGEX, newBlock);\n }\n if (newBlock) {\n return source.replace(/^(Feature:)/m, `${newBlock}$1`);\n }\n return source;\n }\n}\n","/** Dependency-free glob matcher used to select a fallback framework by file path. */\n\n/**\n * @description Tests whether a project-relative path matches a glob pattern. Supports `**`\n * (any characters, including `/`), `*` (any characters except `/`), and `?` (a single\n * non-`/` character). Both `pattern` and `relPath` are normalized to `/`-separated form\n * before matching, so callers on Windows don't need to pre-normalize.\n * @param {string} pattern - Glob pattern, e.g. `\"tests/e2e/**\"`.\n * @param {string} relPath - Project-relative file path to test against the pattern.\n * @returns {boolean} True when `relPath` matches `pattern`.\n */\nexport function matchesGlob(pattern: string, relPath: string): boolean {\n const normalizedPattern = pattern.replace(/\\\\/g, \"/\");\n const normalizedPath = relPath.replace(/\\\\/g, \"/\");\n\n let regexSource = \"\";\n for (let i = 0; i < normalizedPattern.length; i++) {\n const char = normalizedPattern[i];\n if (char === \"*\") {\n if (normalizedPattern[i + 1] === \"*\") {\n regexSource += \".*\";\n i++;\n } else {\n regexSource += \"[^/]*\";\n }\n } else if (char === \"?\") {\n regexSource += \"[^/]\";\n } else if (char !== undefined) {\n regexSource += char.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n }\n\n return new RegExp(`^${regexSource}$`).test(normalizedPath);\n}\n","/**\n * Tag applier strategy for Go test files (*_test.go): writes a `//go:build` constraint\n * using a `mokosh_<tag>` prefix so the tags remain opt-in and don't affect normal builds.\n *\n * Example output (inserted before the package declaration):\n * //go:build mokosh_auth || mokosh_parseArgs\n *\n * The `||` (OR) semantics mean: include this file when ANY of the listed tags is active.\n * Filter at CI time with: `go test -tags mokosh_auth ./...`\n *\n * If the file already has a non-mokosh `//go:build` line (e.g. `//go:build integration`),\n * the strategy leaves it untouched and writes a separate mokosh build tag line.\n *\n * Note: Go has no runtime test-tag system comparable to pytest marks or Vitest tags.\n * Build tags are the closest standard mechanism. Teams preferring a non-build-constraint\n * approach may skip this strategy by not using mokosh with Go test files.\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst MOKOSH_BUILD_TAG_RE = /^\\/\\/go:build mokosh_[^\\n]+\\n/m;\nconst PACKAGE_LINE_RE = /^package\\s+\\S+/m;\n\nfunction buildBuildTag(tags: string[]): string {\n const constraints = tags.map((tag) => `mokosh_${tag}`).join(\" || \");\n return `//go:build ${constraints}\\n`;\n}\n\nfunction readExistingTags(source: string): string[] | null {\n const match = MOKOSH_BUILD_TAG_RE.exec(source);\n if (!match) return null;\n const line = match[0]!;\n const re = /mokosh_([a-zA-Z0-9_-]+)/g;\n const tags: string[] = [];\n let tagMatch = re.exec(line);\n while (tagMatch !== null) {\n if (tagMatch[1]) tags.push(tagMatch[1]);\n tagMatch = re.exec(line);\n }\n return tags;\n}\n\nexport class GoStrategy implements TagApplierStrategy {\n readonly name = \"go\";\n\n canHandle(absPath: string): boolean {\n const base = path.basename(absPath);\n return base.endsWith(\"_test.go\");\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const existing = readExistingTags(source);\n const sortedTags = [...tags].sort();\n\n // Idempotency check\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n if (tags.length === 0) {\n return source.replace(MOKOSH_BUILD_TAG_RE, \"\");\n }\n\n const buildTag = buildBuildTag(sortedTags);\n\n if (existing !== null) {\n return source.replace(MOKOSH_BUILD_TAG_RE, buildTag);\n }\n\n // Insert before the package declaration\n const packageMatch = PACKAGE_LINE_RE.exec(source);\n if (!packageMatch) return source;\n\n const insertAt = packageMatch.index!;\n return source.slice(0, insertAt) + buildTag + \"\\n\" + source.slice(insertAt);\n }\n}\n","/**\n * Tag applier strategy for Jest: writes a `@group` docblock pragma at the top of the file.\n * Jest has no built-in tag/grep mechanism; `jest-runner-groups` is the de-facto standard for\n * file-level tag filtering, reading a `/** @group tagname *\\/` docblock above the imports.\n *\n * Example output:\n * /**\n * * @group auth\n * * @group parseArgs\n * *\\/\n * import { describe, test } from \"@jest/globals\";\n *\n * Filter at CI time with: `jest --group=auth`\n * Requires: `npm install --save-dev jest-runner-groups` and `runner: \"jest-runner-groups\"` in\n * the Jest config.\n * @see https://github.com/facebook-atom/jest-runner-groups\n */\nimport path from \"node:path\";\nimport { TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst GROUP_BLOCK_RE = /^\\/\\*\\*\\n(?: \\* @group .+\\n)+ \\*\\/\\n+/;\nconst GROUP_LINE_RE = /^ \\* @group (.+)$/gm;\n\nfunction buildBlock(tags: string[]): string {\n return [\"/**\", ...tags.map((tag) => ` * @group ${tag}`), \" */\"].join(\"\\n\") + \"\\n\\n\";\n}\n\nfunction readExistingGroups(block: string): string[] {\n const found: string[] = [];\n GROUP_LINE_RE.lastIndex = 0;\n let match = GROUP_LINE_RE.exec(block);\n while (match !== null) {\n if (match[1]) found.push(match[1]);\n match = GROUP_LINE_RE.exec(block);\n }\n return found;\n}\n\nexport class JestStrategy implements TagApplierStrategy {\n readonly name = \"jest\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const match = GROUP_BLOCK_RE.exec(source);\n const existing = match ? readExistingGroups(match[0]) : null;\n const sortedTags = [...tags].sort();\n\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n const stripped = match ? source.slice(match[0].length) : source;\n\n if (tags.length === 0) return stripped;\n\n return buildBlock(sortedTags) + stripped;\n }\n}\n","/**\n * Tag applier strategy for Playwright: injects { tag: [...] } with @ prefix into\n * test.describe/test calls. Playwright uses the singular `tag` option (not `tags`) and\n * conventionally prefixes tag names with `@` (e.g. `@auth`, `@parseArgs`).\n * Filter at CI time with: `playwright test --grep @tagname`\n */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nfunction toPlaywrightLiteral(tags: string[]): string {\n // Playwright tag convention: prefix each name with '@'\n return toArrayLiteral(tags.map((tag) => `@${tag}`));\n}\n\nfunction normaliseExisting(raw: string[]): string[] {\n // Strip @ prefix so we can compare against unprefixed computed tags\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\nexport class PlaywrightStrategy implements TagApplierStrategy {\n readonly name = \"playwright\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return source;\n\n // Idempotency: compare normalised existing tags with computed tags\n const rawExisting = readArrayProp(calls[0]!, \"tag\", sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(normaliseExisting(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tag\", sf)\n : buildInjectReplacement(call, \"tag\", toPlaywrightLiteral(sortedTags), sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\n }\n}\n","/**\n * Tag applier strategy for Python/pytest: writes a module-level `pytestmark` variable.\n * `pytestmark` applies marks to every test in the file without touching individual functions.\n *\n * Example output:\n * import pytest\n * pytestmark = [pytest.mark.auth, pytest.mark.parseArgs]\n *\n * Filter at CI time with: `pytest -m \"auth and parseArgs\"` or `pytest -m auth`\n * @see https://docs.pytest.org/en/stable/how-to/mark.html#marking-whole-classes-or-modules\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\n// Matches: pytestmark = [pytest.mark.foo, pytest.mark.bar]\n// Also handles the single-mark form: pytestmark = pytest.mark.foo\nconst PYTESTMARK_RE = /^pytestmark\\s*=\\s*.+$/m;\n\n// Matches the mokosh-managed import line\nconst PYTEST_IMPORT_RE = /^import pytest\\s*$/m;\n\nfunction buildPytestmark(tags: string[]): string {\n const marks = tags.map((tag) => `pytest.mark.${tag}`).join(\", \");\n return tags.length === 1 ? `pytestmark = pytest.mark.${tags[0]}` : `pytestmark = [${marks}]`;\n}\n\nfunction readExistingMarks(source: string): string[] | null {\n const match = PYTESTMARK_RE.exec(source);\n if (!match) return null;\n const line = match[0];\n // Extract names from pytest.mark.<name>\n const marks: string[] = [];\n const re = /pytest\\.mark\\.([a-zA-Z0-9_-]+)/g;\n let markMatch = re.exec(line);\n while (markMatch !== null) {\n if (markMatch[1]) marks.push(markMatch[1]);\n markMatch = re.exec(line);\n }\n return marks;\n}\n\nexport class PytestStrategy implements TagApplierStrategy {\n readonly name = \"pytest\";\n\n canHandle(absPath: string): boolean {\n return path.extname(absPath).toLowerCase() === \".py\";\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const existing = readExistingMarks(source);\n const sortedTags = [...tags].sort();\n\n // Idempotency check\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n if (tags.length === 0) {\n // Remove pytestmark line (and the pytest import if we added it and it's now unused)\n return source.replace(PYTESTMARK_RE, \"\").replace(/\\n{3,}/g, \"\\n\\n\");\n }\n\n const pytestmarkLine = buildPytestmark(sortedTags);\n\n if (existing !== null) {\n // Replace in-place\n return source.replace(PYTESTMARK_RE, pytestmarkLine);\n }\n\n // Insert after the last import block (or at the top if no imports)\n const hasImport = PYTEST_IMPORT_RE.test(source);\n\n // Find insertion point: after the last top-level import line\n const importBlockEnd = findImportBlockEnd(source);\n\n const before = source.slice(0, importBlockEnd);\n const after = source.slice(importBlockEnd);\n\n const importLine = hasImport ? \"\" : \"import pytest\\n\";\n const separator = before.endsWith(\"\\n\\n\") ? \"\" : \"\\n\";\n\n return before + separator + importLine + pytestmarkLine + \"\\n\" + after;\n }\n}\n\n/** Returns the index just after the last top-level import/from-import line. */\nfunction findImportBlockEnd(source: string): number {\n const lines = source.split(\"\\n\");\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!.trimStart();\n if (line.startsWith(\"import \") || line.startsWith(\"from \")) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine < 0) return 0;\n\n // Compute character offset of the end of that line\n let offset = 0;\n for (let i = 0; i <= lastImportLine; i++) {\n offset += lines[i]!.length + 1; // +1 for \\n\n }\n return offset;\n}\n","/** Tag applier strategy for Vitest: injects { tags: [...] } into describe/test/it calls. */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\n// Strips legacy comment blocks written by older versions of mokosh.\nconst LEGACY_BLOCK_REGEX = /\\/\\/ <mokosh-tags>[\\s\\S]*?\\/\\/ <\\/mokosh-tags>\\n*/;\n\nexport class VitestStrategy implements TagApplierStrategy {\n readonly name = \"vitest\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const stripped = source.replace(LEGACY_BLOCK_REGEX, \"\");\n const sf = ts.createSourceFile(path.basename(absPath), stripped, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return stripped;\n\n // Idempotency check — if first call already has the exact sorted tags, nothing to do\n const existing = readArrayProp(calls[0]!, \"tags\", sf);\n const sortedTags = [...tags].sort();\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return stripped;\n }\n\n const replacements = calls.flatMap((call) => {\n const r =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tags\", sf)\n : buildInjectReplacement(call, \"tags\", toArrayLiteral(sortedTags), sf);\n return r ? [r] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(stripped, replacements) : stripped;\n }\n}\n","/** Strategy interface and default implementation for identifying test nodes in the graph. */\nimport type { StructuredTag } from \"../types/node\";\n\n/**\n * @description Strategy interface for determining whether a graph node represents a test file.\n */\nexport interface TestNodeIdentifier {\n /**\n * @description Returns whether the given node should be treated as a test node.\n * @param {{ category: string; tags: StructuredTag[] }} node - A minimal node descriptor containing its category and structured tags.\n * @returns {boolean} `true` if the node is a test file.\n */\n isTestNode(node: { category: string; tags: StructuredTag[] }): boolean;\n}\n\n/**\n * @description Default implementation that identifies test nodes by `category === \"test\"`\n * or the presence of a structured tag named `\"test\"`.\n */\nexport class DefaultTestNodeIdentifier implements TestNodeIdentifier {\n /**\n * @description Checks the node's category and tag list to determine if it is a test node.\n * @param {{ category: string; tags: StructuredTag[] }} node - A minimal node descriptor containing its category and structured tags.\n * @returns {boolean} `true` if the category is `\"test\"` or any tag is named `\"test\"`.\n */\n public isTestNode(node: { category: string; tags: StructuredTag[] }): boolean {\n return node.category === \"test\" || node.tags.some((tag) => tag.name === \"test\");\n }\n}\n","/** GraphBuilder walks the file system from entry points, parses each reachable file, and assembles the dependency graph. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGitFileStats } from \"../git.js\";\nimport { getTestPatterns } from \"../parser/classify.js\";\nimport { type LockFileData, loadLockFile } from \"../parser/lockfile.js\";\nimport { getFileType, parseFile } from \"../parser.js\";\nimport type { DependencyGraph } from \"../types/graph\";\nimport type { CallEdge, FileNode, ImportEdge } from \"../types/node\";\nimport {\n enrichCoverage,\n enrichExportUsage,\n enrichLibraryTags,\n enrichTestedBy,\n enrichTestNodeTags,\n} from \"./enrichment.js\";\nimport { Graph } from \"./model.js\";\nimport { DefaultResolver, type PathResolver } from \"./resolver.js\";\n\n/** Conventional top-level test-directory names probed as siblings between the entry-derived scan root and `rootDir`. */\nconst CONVENTIONAL_TEST_DIR_NAMES = [\"tests\", \"test\", \"__tests__\", \"specs\", \"spec\"];\n\n/**\n * @description Finds the deepest directory that contains every path in `absPaths`, clamped\n * so the result is never outside `rootDir`. Used to scope the test-file discovery walk to\n * the subtree actually reachable from the given entry points, instead of always walking the\n * full project root (which, for a nested sub-project under a much larger `rootDir`, would\n * sweep in unrelated files).\n * @param absPaths - Absolute file paths (typically resolved entry points).\n * @param rootDir - Absolute project root; acts as an upper bound for the result.\n * @returns The common ancestor directory, or `rootDir` if `absPaths` is empty or resolves outside it.\n */\nfunction commonAncestorDir(absPaths: string[], rootDir: string): string {\n if (absPaths.length === 0) return rootDir;\n\n const segmentLists = absPaths.map((p) => path.dirname(p).split(path.sep));\n let common = segmentLists[0]!;\n for (const segments of segmentLists.slice(1)) {\n let i = 0;\n while (i < common.length && i < segments.length && common[i] === segments[i]) i++;\n common = common.slice(0, i);\n }\n const candidate = common.join(path.sep) || path.sep;\n\n const rel = path.relative(rootDir, candidate);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) return rootDir;\n return candidate;\n}\n\n/**\n * @description Builds a dependency graph by recursively walking the file system from a set of entry points.\n *\n * Responsibilities:\n * - Parsing each reachable source file via {@link parseFile}\n * - Resolving raw import specifiers to actual file paths via a {@link PathResolver}\n * - Reusing unchanged nodes from a previous graph (incremental build)\n * - Annotating external imports with lock-file versions\n * - Applying post-build enrichment (test-node tags)\n *\n * **SRP note:** `resolveImports` intentionally doubles as the recursion trigger —\n * it calls `processFile` on each local dependency as it resolves it. This keeps the\n * traversal depth-first and avoids a separate queue, at the cost of two concerns\n * living in one method.\n *\n * **DIP note:** Only `PathResolver` is abstracted. `fs`, parsers, and enrichment\n * functions are concrete imports — sufficient for a build-time tool where the call\n * sites are stable and swapping them out has no real use case.\n */\nexport class GraphBuilder {\n private graph: DependencyGraph = { nodes: new Map() };\n private visited = new Set<string>();\n private readonly previousGraph: Graph | null = null;\n private readonly resolver: PathResolver;\n private lockFile: LockFileData | null = null;\n private progressCallback?: (count: number) => void;\n\n /**\n * @param rootDir - Absolute path to the project root; all node paths in the graph are relative to this.\n * @param previousGraph - Optional graph from a prior run. Nodes whose `mtime` and `size` match are reused as-is, making incremental builds significantly faster.\n * @param resolver - Strategy for turning raw import specifiers into absolute file paths. Defaults to {@link DefaultResolver}, which handles relative paths, tsconfig aliases, and node_modules.\n * @param progressCallback - Called every 100 files processed; useful for rendering a progress indicator in long-running CLI builds.\n * @param gitStats - When true, fetches `commitCount90d` and `lastAuthor` for each cache-missed file via git log.\n * @param coverageMap - Pre-loaded coverage map (relative path → line %). When non-empty, populates `coveragePct` on each node after the graph is built.\n */\n constructor(\n private rootDir: string,\n previousGraph: Graph | null = null,\n resolver?: PathResolver,\n progressCallback?: (count: number) => void,\n private readonly enableGitStats = false,\n private readonly coverageMap: Map<string, number> = new Map(),\n ) {\n this.previousGraph = previousGraph;\n this.resolver = resolver || new DefaultResolver(rootDir);\n this.lockFile = loadLockFile(rootDir);\n if (progressCallback) {\n this.progressCallback = progressCallback;\n }\n }\n\n /**\n * @description Starts the graph build from the given entry points and returns the completed graph.\n *\n * Each entry point triggers a depth-first traversal: imports are resolved, unvisited\n * local files are parsed, and the process continues until the full reachable subgraph\n * is covered. Test-node tags are applied as a final post-processing step because they\n * depend on the fully connected graph (e.g. a file is \"test\" if something imports it\n * with a `.test.` path, which can only be known after all edges are resolved).\n * @param entryPoints - File paths to start from. Relative paths are resolved against `rootDir`.\n * @returns The completed, enriched dependency graph.\n */\n public async build(entryPoints: string[]): Promise<Graph> {\n const entryPaths = entryPoints.map((entry) =>\n path.isAbsolute(entry) ? entry : path.resolve(this.rootDir, entry),\n );\n for (const entryPath of entryPaths) {\n await this.processFile(entryPath);\n }\n\n // Test files are never reachable from library entry points (imports flow source→test,\n // not the other way around). Scan for them explicitly so enrichTestedBy has data.\n // Scoped to the entry points' common ancestor (plus conventional sibling test dirs) rather\n // than the full rootDir, so a nested sub-project scanned from a much larger rootDir doesn't\n // pull in unrelated files elsewhere in the tree.\n await this.processTestFiles(commonAncestorDir(entryPaths, this.rootDir));\n\n if (this.progressCallback && this.visited.size >= 100) {\n process.stderr.write(`\\nDone. Total processed: ${this.visited.size} nodes.\\n`);\n }\n\n enrichTestNodeTags(this.graph.nodes);\n enrichTestedBy(this.graph.nodes);\n enrichExportUsage(this.graph.nodes);\n if (this.coverageMap.size > 0) enrichCoverage(this.graph.nodes, this.coverageMap);\n return new Graph(this.graph.nodes);\n }\n\n /**\n * @description Scans the file system for test files and processes them into the graph.\n * Test files are never reachable from library entry points, so they must be discovered\n * separately; without this pass `enrichTestedBy` would have no data to work with.\n * @param scanRoot - Directory to walk for test files; the entry points' common ancestor,\n * clamped to `rootDir`. Conventional sibling test directories (`tests/`, `__tests__/`, …)\n * found between `scanRoot` and `rootDir` are also walked, so a top-level test directory\n * alongside a `src/` entry point is still discovered even though it falls outside `scanRoot`.\n */\n private async processTestFiles(scanRoot: string): Promise<void> {\n const patterns = getTestPatterns();\n const ignoreDirs = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n ]);\n const walk = async (dir: string): Promise<void> => {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!ignoreDirs.has(entry.name)) await walk(fullPath);\n } else if (entry.isFile() && patterns.some((pattern) => entry.name.includes(pattern))) {\n await this.processFile(fullPath);\n }\n }\n };\n await walk(scanRoot);\n\n let dir = scanRoot;\n while (dir !== this.rootDir) {\n const parent = path.dirname(dir);\n if (parent === dir) break;\n for (const name of CONVENTIONAL_TEST_DIR_NAMES) {\n const candidate = path.join(parent, name);\n try {\n if (fs.statSync(candidate).isDirectory()) await walk(candidate);\n } catch {\n // not present\n }\n }\n dir = parent;\n }\n }\n\n /**\n * @description Parses a single file and registers it in the graph, then recurses into its imports.\n *\n * The `visited` guard prevents re-processing files encountered via multiple import paths\n * (diamond dependencies). It is set before any async work so concurrent calls on the same\n * path — if this ever runs with parallelism — cannot race.\n * @param filePath - Absolute path of the file to process.\n */\n private async processFile(filePath: string) {\n if (this.visited.has(filePath)) return;\n this.visited.add(filePath);\n\n this.showProgress();\n\n const stats = fs.statSync(filePath, { throwIfNoEntry: false });\n if (!stats?.isFile()) return;\n\n const relativePath = path.relative(this.rootDir, filePath);\n const node = await this.getNode(filePath, relativePath, stats);\n\n node.imports = await this.resolveImports(filePath, node.imports);\n\n this.graph.nodes.set(node.path, node);\n }\n\n /**\n * @description Returns the `FileNode` for a file, either from the incremental cache or by\n * parsing it fresh. Cache hit requires both `mtime` and `size` to match — size guards\n * against tools that restore a previous file version with an identical timestamp.\n * @param filePath - Absolute path of the file.\n * @param relativePath - Path relative to `rootDir`, used as the node key.\n * @param stats - File system stats for cache validation and node metadata.\n * @returns The parsed or cached `FileNode`.\n */\n private async getNode(\n filePath: string,\n relativePath: string,\n stats: fs.Stats,\n ): Promise<FileNode> {\n const cachedNode = this.previousGraph?.nodes.get(relativePath);\n if (cachedNode && cachedNode.mtime === stats.mtimeMs && cachedNode.size === stats.size) {\n return { ...cachedNode };\n }\n\n const parsed = await this.tryParse(filePath, relativePath);\n if (!parsed) return this.makeStubNode(filePath, relativePath, stats);\n\n enrichLibraryTags(parsed.imports, parsed.tags);\n const callEdges = this.resolveCallEdges(filePath, parsed.rawCallEdges);\n const node = this.buildNode(filePath, relativePath, stats, parsed, callEdges);\n this.attachGitStats(node, relativePath);\n return node;\n }\n\n /**\n * @description Reads and parses a file, returning `null` on failure and emitting a warning\n * to stderr so the surrounding graph build can continue with a stub.\n * @param filePath - Absolute path of the file to parse.\n * @param relativePath - Relative path used only in the warning message.\n * @returns The parse result, or `null` if parsing threw.\n */\n private async tryParse(\n filePath: string,\n relativePath: string,\n ): Promise<Awaited<ReturnType<typeof parseFile>> | null> {\n const content = fs.readFileSync(filePath, \"utf-8\");\n try {\n return await parseFile(filePath, content);\n } catch (err) {\n process.stderr.write(`\\nWarning: failed to parse ${relativePath}: ${err}\\n`);\n return null;\n }\n }\n\n /**\n * @description Builds a minimal stub `FileNode` for a file that could not be parsed,\n * keeping the graph structurally intact while surfacing the failure via category `\"other\"`.\n * @param filePath - Absolute path, used to determine the file type.\n * @param relativePath - Used as the node's path key.\n * @param stats - Provides `mtime` and `size` for future cache comparisons.\n * @returns A `FileNode` with empty imports, exports, and tags.\n */\n private makeStubNode(filePath: string, relativePath: string, stats: fs.Stats): FileNode {\n return {\n path: relativePath,\n type: getFileType(filePath),\n category: \"other\",\n imports: [],\n exports: [],\n tags: [],\n mtime: stats.mtimeMs,\n size: stats.size,\n };\n }\n\n /**\n * @description Resolves raw call-edge specifiers to project-relative file paths,\n * silently dropping any specifier the resolver cannot map.\n * @param filePath - Absolute path of the file that owns the call edges.\n * @param rawCallEdges - Unresolved call edges from the parser output.\n * @returns Resolved `CallEdge` array containing only internal (non-external) edges.\n */\n private resolveCallEdges(\n filePath: string,\n rawCallEdges: Awaited<ReturnType<typeof parseFile>>[\"rawCallEdges\"],\n ): CallEdge[] {\n const callEdges: CallEdge[] = [];\n for (const rce of rawCallEdges ?? []) {\n try {\n const resolved = this.resolver.resolve(filePath, rce.toSpecifier);\n if (resolved && !resolved.isExternal) {\n callEdges.push({\n from: rce.from,\n to: rce.to,\n toFile: path.relative(this.rootDir, resolved.path),\n });\n }\n } catch {\n // unresolvable specifier — silent\n }\n }\n return callEdges;\n }\n\n /**\n * @description Assembles the final `FileNode` from parsed data and resolved call edges.\n * @param filePath - Absolute path, used to determine the file type.\n * @param relativePath - Used as the node's path key.\n * @param stats - Provides `mtime` and `size`.\n * @param parsed - Structured output from the parser.\n * @param callEdges - Already-resolved call edges to attach when non-empty.\n * @returns A fully populated `FileNode` ready to be inserted into the graph.\n */\n private buildNode(\n filePath: string,\n relativePath: string,\n stats: fs.Stats,\n parsed: Awaited<ReturnType<typeof parseFile>>,\n callEdges: CallEdge[],\n ): FileNode {\n const {\n imports,\n exports,\n tags,\n category,\n description,\n complexity,\n cognitiveComplexity,\n functions,\n } = parsed;\n return {\n path: relativePath,\n type: getFileType(filePath),\n category,\n imports,\n exports,\n tags,\n mtime: stats.mtimeMs,\n size: stats.size,\n ...(description !== undefined ? { description } : {}),\n ...(callEdges.length > 0 ? { callEdges } : {}),\n ...(complexity !== undefined ? { complexity } : {}),\n ...(cognitiveComplexity !== undefined ? { cognitiveComplexity } : {}),\n ...(functions !== undefined ? { functions } : {}),\n };\n }\n\n /**\n * @description Enriches a node with git activity metadata when `enableGitStats` is on,\n * silently skipping files not tracked by git or when git is unavailable.\n * @param node - The node to mutate in place.\n * @param relativePath - Project-relative path passed to the git helper.\n */\n private attachGitStats(node: FileNode, relativePath: string): void {\n if (!this.enableGitStats) return;\n try {\n const git = getGitFileStats(this.rootDir, relativePath);\n node.commitCount90d = git.commitCount90d;\n if (git.lastAuthor !== undefined) node.lastAuthor = git.lastAuthor;\n } catch {\n // git not available or file not tracked — silent\n }\n }\n\n /**\n * @description Resolves each import's raw specifier to a concrete path and, for local imports,\n * triggers recursive processing of the target file.\n *\n * Combining resolution with recursion (rather than two separate passes) keeps the\n * traversal depth-first, which improves cache locality during parsing. The trade-off\n * is that this method now owns two concerns: path resolution and graph traversal.\n *\n * For external imports (node_modules), the package name is extracted from the specifier\n * and matched against the lock file to attach a resolved version string. Scoped packages\n * (`@scope/pkg/deep`) are normalised to the two-segment package name before lookup.\n *\n * Imports that the resolver cannot map to any path are silently dropped — this covers\n * dynamic specifiers, virtual modules, and unsupported module systems.\n * @param filePath - Absolute path of the file that owns these imports.\n * @param imports - Raw import edges as produced by the parser, with unresolved `toPath` values.\n * @returns The same edges with `toPath`, `isExternal`, and optionally `version` filled in.\n */\n private async resolveImports(filePath: string, imports: ImportEdge[]): Promise<ImportEdge[]> {\n const resolvedImports: ImportEdge[] = [];\n\n for (const imp of imports) {\n const results = this.resolver.resolveAll(filePath, imp.rawSpecifier);\n if (results.length === 0) continue;\n\n for (const resolved of results) {\n const edge: ImportEdge = {\n ...imp,\n toPath: resolved.isExternal ? resolved.path : path.relative(this.rootDir, resolved.path),\n isExternal: resolved.isExternal,\n };\n\n if (resolved.isWorkspace) {\n edge.isWorkspace = true;\n edge.workspacePackage = resolved.workspacePackage;\n }\n\n if (resolved.isExternal) {\n this.attachLockfileVersion(edge);\n } else {\n await this.processFile(resolved.path);\n }\n\n resolvedImports.push(edge);\n }\n }\n\n return resolvedImports;\n }\n\n /**\n * @description Looks up the package version from the lock file and attaches it to the import edge.\n * Scoped packages (`@scope/pkg/deep/path`) are normalised to their two-segment name before lookup.\n * @param imp - The external import edge to annotate; mutated in place.\n */\n private attachLockfileVersion(imp: ImportEdge): void {\n if (!this.lockFile) return;\n const libName = imp.rawSpecifier.startsWith(\"@\")\n ? imp.rawSpecifier.split(\"/\").slice(0, 2).join(\"/\")\n : (imp.rawSpecifier.split(\"/\")[0] as string);\n const dep = libName ? this.lockFile.dependencies[libName] : undefined;\n if (dep) imp.version = dep.version;\n }\n\n /**\n * @description Fires the progress callback every 100 files to avoid flooding the caller with updates.\n */\n private showProgress() {\n if (this.progressCallback && this.visited.size % 100 === 0) {\n this.progressCallback(this.visited.size);\n }\n }\n}\n","/** Git integration: changed-file detection via GitProvider and per-file commit activity stats via getGitFileStats. */\nimport { execSync } from \"node:child_process\";\n\n/**\n * @description Contract for querying changed files from a version-control backend.\n * Abstracted so the CLI and MCP server can be tested without a live git repository.\n */\nexport interface GitProvider {\n getChangedFiles(): string[];\n}\n\n/**\n * @description Default `GitProvider` that shells out to the git CLI to discover\n * modified, staged, and untracked files in the current repository.\n */\nexport class DefaultGitProvider implements GitProvider {\n /**\n * @description Returns a deduplicated list of all modified, staged, and untracked files by running three git commands.\n * Returns an empty array if not inside a git repository or if git is unavailable.\n * @returns Relative file paths as reported by git, deduplicated across all three query types.\n */\n public getChangedFiles(): string[] {\n try {\n const commands = [\n \"git diff --name-only\",\n \"git diff --cached --name-only\",\n \"git ls-files --others --exclude-standard\",\n ];\n\n const allFiles = commands.flatMap((cmd) => {\n try {\n const output = execSync(cmd, { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n return output\n .split(\"\\n\")\n .map((filePath) => filePath.trim())\n .filter((filePath) => filePath !== \"\");\n } catch {\n return [];\n }\n });\n\n return Array.from(new Set(allFiles));\n } catch (error) {\n console.error(\"Error getting git diff:\", error);\n return [];\n }\n }\n}\n\n/**\n * @description Commit activity metadata for a single file, used to surface churn and ownership signals.\n */\nexport interface GitFileStats {\n commitCount90d: number;\n lastAuthor: string | undefined;\n}\n\n/**\n * @description Queries git log to compute commit frequency and last author for a file over the past 90 days.\n * @param rootDir - Absolute path to the repository root, passed to `git -C` so the command works from any cwd.\n * @param relativePath - Path to the file relative to `rootDir`.\n * @returns Commit count and the email of the most recent author, or `undefined` if the file has no history.\n */\nexport function getGitFileStats(rootDir: string, relativePath: string): GitFileStats {\n const output = execSync(\n `git -C \"${rootDir}\" log --follow --format=\"%ae\" --since=\"90 days ago\" -- \"${relativePath}\"`,\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n );\n const lines = output.split(\"\\n\").filter(Boolean);\n return { commitCount90d: lines.length, lastAuthor: lines[0] };\n}\n","/** Parses npm, Yarn, and pnpm lock files to extract installed package versions for import-edge annotation. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\n\n/**\n * Represents the parsed data from a lock file.\n */\nexport interface LockFileData {\n /**\n * Map of package names to their version and nested dependencies.\n */\n dependencies: Record<string, { version: string; dependencies?: Record<string, string> }>;\n}\n\ninterface PkgData {\n version: string;\n dependencies?: Record<string, string>;\n}\n\ninterface PackageLock {\n packages?: Record<string, PkgData>;\n dependencies?: Record<string, PkgData>;\n}\n\n/**\n * @description Strips the `@version` suffix from a package descriptor string,\n * correctly handling scoped packages like `@scope/pkg@1.0.0` where the leading `@`\n * must not be treated as the version separator.\n * @param descriptor - The raw package descriptor string, e.g. `react@^18.0` or `@scope/pkg@1.0.0`.\n * @returns The package name without the version suffix, or the original string if no separator was found.\n */\nfunction stripVersionSuffix(descriptor: string): string {\n const lastAt = descriptor.lastIndexOf(\"@\");\n return lastAt > 0 ? descriptor.substring(0, lastAt) : descriptor;\n}\n\n/**\n * @description Parses the header line of a yarn classic block into deduplicated package names.\n * Strips trailing colons, surrounding quotes, and version descriptors from comma-separated\n * entries like `\"react@^17.0\", \"react@^18.0\":`.\n * @param line - A raw yarn classic header line, e.g. `\"react@^17.0\", \"react@^18.0\":`.\n * @returns Array of package names extracted from the descriptors.\n */\nfunction parseYarnDescriptors(line: string): string[] {\n return line\n .replace(/:$/, \"\")\n .split(\",\")\n .map((part) => {\n let trimmed = part.trim();\n if (trimmed.startsWith('\"')) trimmed = trimmed.slice(1);\n if (trimmed.endsWith('\"')) trimmed = trimmed.slice(0, -1);\n return stripVersionSuffix(trimmed);\n })\n .filter(Boolean);\n}\n\n/**\n * @description Extracts a name and version from a pnpm package ID.\n * pnpm IDs use formats like `/pkg@version`, `/@scope/pkg@version`, or `pkg@version`;\n * when the ID encodes a version it is used as a fallback when `pkgVersion` is absent.\n * @param id - The pnpm package ID, e.g. `/lodash@4.17.21` or `/@scope/pkg@1.0.0`.\n * @param pkgVersion - The explicit version from the lockfile entry; takes priority over the version embedded in `id`.\n * @returns An object with the extracted `name` and resolved `version`.\n */\nfunction parsePnpmId(id: string, pkgVersion: string): { name: string; version: string } {\n const raw = id.startsWith(\"/\") ? id.slice(1) : id;\n const lastAt = raw.lastIndexOf(\"@\");\n if (lastAt > 0) {\n return { name: raw.substring(0, lastAt), version: pkgVersion || raw.substring(lastAt + 1) };\n }\n return { name: raw, version: pkgVersion };\n}\n\n/**\n * @description Attempts to parse a Yarn Berry (v2+) YAML lockfile. Returns `null` when\n * YAML parsing fails so the caller can fall back to the classic text-format parser.\n * @param content - Raw text content of the `yarn.lock` file.\n * @returns Parsed lock file data on success, or `null` if YAML parsing fails.\n */\nfunction tryParseYarnBerry(content: string): LockFileData | null {\n try {\n const lock = yaml.load(content) as Record<string, PkgData>;\n const result: LockFileData = { dependencies: {} };\n for (const [key, value] of Object.entries(lock)) {\n if (key === \"__metadata\" || !value?.version) continue;\n for (const part of key.split(\", \")) {\n const name = stripVersionSuffix(part);\n if (name) {\n result.dependencies[name] = {\n version: value.version,\n ...(value.dependencies !== undefined && { dependencies: value.dependencies }),\n };\n }\n }\n }\n return result;\n } catch (_e) {\n return null;\n }\n}\n\n/**\n * @description Parses a Yarn v1 classic lockfile by splitting the content into blank-line-separated\n * blocks. Each block's first non-comment, non-indented line carries the package descriptors;\n * a `version \"...\"` line within the same block provides the resolved version.\n * @param content - Raw text content of the `yarn.lock` file.\n * @returns Parsed lock file data with all discovered package versions.\n */\nfunction parseYarnClassic(content: string): LockFileData {\n const result: LockFileData = { dependencies: {} };\n\n for (const block of content.split(/\\n\\n+/)) {\n const lines = block\n .split(\"\\n\")\n .filter((line) => line.trim().length > 0 && !line.trim().startsWith(\"#\"));\n if (lines.length < 2) continue;\n\n const header = lines[0];\n if (!header || header.startsWith(\" \")) continue;\n\n const names = parseYarnDescriptors(header);\n if (names.length === 0) continue;\n\n const versionLine = lines.find((line) => line.trim().startsWith('version \"'));\n const version = versionLine?.match(/version \"(.*?)\"/)?.[1] ?? \"\";\n\n for (const name of names) {\n result.dependencies[name] = { version };\n }\n }\n\n return result;\n}\n\n/**\n * @description Parses a `package-lock.json` file. Supports v1/v2 (`dependencies` key) and\n * v3 (`packages` key with `node_modules/` prefixed paths); nested `node_modules` entries\n * (e.g. `node_modules/a/node_modules/b`) are skipped.\n * @param filePath - Absolute path to the `package-lock.json` file.\n * @returns Parsed lock file data with all top-level dependencies and their versions.\n */\nexport function parsePackageLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const lock = JSON.parse(content) as PackageLock;\n const result: LockFileData = { dependencies: {} };\n\n if (lock.packages) {\n for (const [pkgPath, pkgData] of Object.entries(lock.packages)) {\n if (!pkgPath.startsWith(\"node_modules/\")) continue;\n const name = pkgPath.replace(\"node_modules/\", \"\");\n if (name.includes(\"node_modules/\")) continue;\n result.dependencies[name] = {\n version: pkgData.version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n } else if (lock.dependencies) {\n for (const [name, pkgData] of Object.entries(lock.dependencies)) {\n result.dependencies[name] = {\n version: pkgData.version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n }\n\n return result;\n}\n\n/**\n * @description Parses a `yarn.lock` file. Detects Yarn Berry (v2+) by the presence of\n * `__metadata:` and attempts YAML parsing first; falls back to the v1 classic text parser\n * when YAML parsing fails.\n * @param filePath - Absolute path to the `yarn.lock` file.\n * @returns Parsed lock file data with all discovered package versions.\n */\nexport function parseYarnLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n if (content.includes(\"__metadata:\")) {\n const berryResult = tryParseYarnBerry(content);\n if (berryResult !== null) return berryResult;\n }\n\n return parseYarnClassic(content);\n}\n\n/**\n * @description Parses a `pnpm-lock.yaml` file. Handles the `packages` section (v6+) where\n * package IDs encode the name and version, and the root-level `dependencies` section (v5)\n * as a fallback for packages not already captured from `packages`.\n * @param filePath - Absolute path to the `pnpm-lock.yaml` file.\n * @returns Parsed lock file data, or an empty dependencies map if YAML parsing fails.\n */\nexport function parsePnpmLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const result: LockFileData = { dependencies: {} };\n\n try {\n const lock = yaml.load(content) as {\n packages?: Record<string, PkgData>;\n dependencies?: Record<string, string | PkgData>;\n };\n\n if (lock.packages) {\n for (const [id, pkgData] of Object.entries(lock.packages)) {\n const { name, version } = parsePnpmId(id, pkgData.version);\n if (name) {\n result.dependencies[name] = {\n version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n }\n }\n\n if (lock.dependencies) {\n for (const [name, versionData] of Object.entries(lock.dependencies)) {\n if (result.dependencies[name]) continue;\n const version = typeof versionData === \"string\" ? versionData : versionData.version;\n result.dependencies[name] = { version: version || \"\" };\n }\n }\n } catch (_e) {\n // Ignore YAML errors\n }\n\n return result;\n}\n\n/**\n * @description Detects and loads the first supported lock file found in `rootDir`.\n * Checks for `package-lock.json`, `yarn.lock`, and `pnpm-lock.yaml` in that order.\n * @param rootDir - The project root directory to search for lock files.\n * @returns Parsed lock file data from the first detected lock file, or `null` if none is found.\n */\nexport function loadLockFile(rootDir: string): LockFileData | null {\n const candidates: [string, (lockFilePath: string) => LockFileData][] = [\n [\"package-lock.json\", parsePackageLock],\n [\"yarn.lock\", parseYarnLock],\n [\"pnpm-lock.yaml\", parsePnpmLock],\n ];\n\n for (const [filename, parser] of candidates) {\n const filePath = path.join(rootDir, filename);\n if (fs.existsSync(filePath)) return parser(filePath);\n }\n\n return null;\n}\n","/** Maps file extensions to FileType enum values for use by the parser registry and graph builder. */\nimport path from \"node:path\";\nimport type { FileType } from \"../types/parse\";\n\n/**\n * @description Maps a file path's extension to its canonical `FileType` identifier,\n * returning `\"unknown\"` for unrecognised or unsupported extensions.\n * @param filePath - Absolute or relative path to the source file.\n * @returns The `FileType` string corresponding to the file's language.\n */\nexport function getFileType(filePath: string): FileType {\n const ext = path.extname(filePath).toLowerCase();\n switch (ext) {\n case \".js\":\n case \".jsx\":\n case \".mjs\":\n case \".cjs\":\n return \"javascript\";\n case \".ts\":\n case \".tsx\":\n return \"typescript\";\n case \".css\":\n return \"css\";\n case \".scss\":\n case \".sass\":\n return \"scss\";\n case \".less\":\n return \"less\";\n case \".styl\":\n return \"stylus\";\n case \".coffee\":\n return \"coffeescript\";\n case \".ls\":\n return \"livescript\";\n case \".lua\":\n return \"lua\";\n case \".py\":\n return \"python\";\n case \".go\":\n return \"go\";\n case \".java\":\n case \".cpp\":\n case \".cc\":\n case \".cxx\":\n case \".c\":\n return \"unknown\";\n case \".feature\":\n return \"gherkin\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * @description Checks whether an import specifier refers to a stylesheet by examining\n * its extension, covering CSS, SCSS/Sass, Less, and Stylus.\n * @param specifier - The raw import specifier string from source code.\n * @returns `true` if the specifier's extension is a known stylesheet format.\n */\nexport function isStyleFile(specifier: string): boolean {\n const ext = path.extname(specifier).toLowerCase();\n return [\".css\", \".scss\", \".sass\", \".less\", \".styl\"].includes(ext);\n}\n","/** Parses CoffeeScript files to extract import edges and tag annotations. */\nimport coffee from \"coffeescript\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\n\ninterface CoffeeNode {\n constructor: { name: string };\n source?: { value: string };\n variable?: { base?: { value: string } };\n args?: Array<{ base?: { value: string } }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Scans raw source text for `@tag <name>` comment annotations and collects\n * the tag names. Runs before category resolution so `@tag test` can influence classification.\n * @param content - Raw source text to scan.\n * @returns Set of tag name strings found in `@tag` annotations.\n */\nfunction extractTags(content: string): Set<string> {\n const tags = new Set<string>();\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let match = tagRegex.exec(content);\n while (match !== null) {\n if (match[1]) tags.add(match[1]);\n match = tagRegex.exec(content);\n }\n return tags;\n}\n\n/**\n * @description Determines whether a file is a test or production-logic file by checking\n * path naming conventions (`.test.`, `.spec.`) and explicit `@tag test` annotations.\n * @param filePath - Path to the file being classified.\n * @param tags - Tag names extracted from the file's content.\n * @returns `\"test\"` if the file is a test file, `\"logic\"` otherwise.\n */\nfunction resolveCategory(filePath: string, tags: Set<string>): \"test\" | \"logic\" {\n const lower = filePath.toLowerCase();\n if (lower.includes(\".test.\") || lower.includes(\".spec.\") || tags.has(\"test\")) {\n return \"test\";\n }\n return \"logic\";\n}\n\n/**\n * @description Builds an `ImportEdge` from a CoffeeScript static `import` declaration AST node.\n * @param filePath - Source file path stamped onto the edge.\n * @param node - CoffeeScript AST node representing an `ImportDeclaration`.\n * @returns An `ImportEdge` for the import, or `null` if the node carries no source value.\n */\nfunction edgeFromImportDeclaration(filePath: string, node: CoffeeNode): ImportEdge | null {\n const specifier = node.source?.value;\n if (!specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"static\",\n };\n}\n\n/**\n * @description Builds an `ImportEdge` from a CoffeeScript `require()` call AST node.\n * @param filePath - Source file path stamped onto the edge.\n * @param node - CoffeeScript AST node representing a `Call`.\n * @returns An `ImportEdge` for the require call, or `null` if the node is not a `require` call or has no specifier.\n */\nfunction edgeFromRequireCall(filePath: string, node: CoffeeNode): ImportEdge | null {\n const isRequire = node.variable?.base?.value === \"require\";\n const specifier = node.args?.[0]?.base?.value;\n if (!isRequire || !specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n };\n}\n\n/**\n * @description Inspects a single CoffeeScript AST node and appends any discovered import edge\n * to the accumulator array. Handles both `ImportDeclaration` and `Call` (require) node types.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to inspect.\n * @param out - Accumulator array that receives any discovered edge.\n */\nfunction visitNode(filePath: string, node: CoffeeNode, out: ImportEdge[]): void {\n const className = node.constructor?.name;\n if (className === \"ImportDeclaration\") {\n const edge = edgeFromImportDeclaration(filePath, node);\n if (edge) out.push(edge);\n } else if (className === \"Call\") {\n const edge = edgeFromRequireCall(filePath, node);\n if (edge) out.push(edge);\n }\n}\n\n/**\n * @description Recursively walks the CoffeeScript AST and collects all import edges into `out`.\n * Skips `locationData` keys to prevent infinite cycles on circular metadata references.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to walk.\n * @param out - Accumulator array that receives all discovered edges.\n */\nfunction traverse(filePath: string, node: CoffeeNode, out: ImportEdge[]): void {\n if (!node || typeof node !== \"object\") return;\n visitNode(filePath, node, out);\n for (const key in node) {\n if (key === \"locationData\") continue;\n const child = node[key];\n if (!child || typeof child !== \"object\") continue;\n if (Array.isArray(child)) {\n for (const c of child) traverse(filePath, c as CoffeeNode, out);\n } else {\n traverse(filePath, child as CoffeeNode, out);\n }\n }\n}\n\n/**\n * @description Parses a CoffeeScript source file and extracts its import edges, comment-marker\n * tags, and file category. Uses the CoffeeScript compiler's `nodes()` API for full AST\n * traversal, capturing both ES `import` declarations and CommonJS `require()` calls.\n * Falls back to an empty import list if the file fails to parse.\n * @param filePath - Absolute or project-relative path to the `.coffee` file.\n * @param content - Raw source text of the file.\n * @returns Parsed imports, empty exports list, extracted tags, and resolved category.\n */\nexport function parseCoffeeScript(filePath: string, content: string): ParseResult {\n const tags = extractTags(content);\n const category = resolveCategory(filePath, tags);\n const imports: ImportEdge[] = [];\n\n try {\n traverse(filePath, coffee.nodes(content) as unknown as CoffeeNode, imports);\n } catch (_e) {\n // coffeescript compiler throws on invalid syntax; return what we have\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Gherkin .feature files to extract scenario tag annotations using the official Cucumber parser. */\nimport { AstBuilder, GherkinClassicTokenMatcher, Parser } from \"@cucumber/gherkin\";\nimport { IdGenerator } from \"@cucumber/messages\";\nimport { registerParser } from \"../registry\";\nimport type { ParseResult } from \"../types\";\n\nconst uuidFn = IdGenerator.uuid();\n\n/**\n * @description Parses a Gherkin `.feature` file using the official Cucumber AST builder.\n * Walks the feature, scenario, example, and rule hierarchy to collect all `@tag` annotations.\n * Gherkin files are always categorized as `\"test\"`.\n * @param _filePath - Path to the feature file; used only in error messages.\n * @param content - Raw Gherkin source text.\n * @returns A `ParseResult` with no imports, no exports, all collected tags, and category `\"test\"`.\n */\nexport function parseGherkin(_filePath: string, content: string): ParseResult {\n const rawTags = new Set<string>();\n\n try {\n const builder = new AstBuilder(uuidFn);\n const matcher = new GherkinClassicTokenMatcher();\n const parser = new Parser(builder, matcher);\n\n const gherkinDocument = parser.parse(content);\n\n if (gherkinDocument.feature) {\n // Feature tags\n gherkinDocument.feature.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n\n // Child tags (Scenarios, Rules, etc.)\n gherkinDocument.feature.children.forEach((child) => {\n if (child.scenario) {\n child.scenario.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n\n // Example tags\n child.scenario.examples.forEach((example) => {\n example.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n });\n }\n\n if (child.rule) {\n child.rule.children.forEach((ruleChild) => {\n if (ruleChild.scenario) {\n ruleChild.scenario.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n }\n });\n }\n });\n }\n } catch (error) {\n console.warn(`[GherkinParser] Failed to parse ${_filePath}:`, error);\n }\n\n return {\n imports: [],\n exports: [],\n tags: Array.from(rawTags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category: \"test\",\n };\n}\n\nregisterParser(\"gherkin\", parseGherkin);\n","/** Parses Go source files using the Lezer parser to extract import paths and tag annotations. */\nimport path from \"node:path\";\nimport { parser } from \"@lezer/go\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\nconst TAG_RE = /\\/\\/\\s*@tag\\s+([a-zA-Z0-9_-]+)/;\nconst BUILD_NEW_RE = /^\\/\\/go:build\\s+(.+)$/;\nconst BUILD_OLD_RE = /^\\/\\/\\s*\\+build\\s+(.+)$/;\n\n/**\n * @description Parses a Go source file using the Lezer Go grammar to extract import edges,\n * exported symbols, `// @tag` comment markers, and file category. All imports are marked\n * external — local package resolution requires `go.mod` context not available at parse time.\n * @param {string} filePath - Path to the `.go` file; used for test-file classification by basename convention.\n * @param {string} content - Raw Go source text.\n * @returns {ParseResult} Parsed imports, top-level exports, comment-marker tags, and resolved category.\n */\nexport function parseGo(filePath: string, content: string): ParseResult {\n const imports: ImportEdge[] = [];\n const exportMap = new Map<string, ExportedSymbol>();\n const tags = new Set<string>();\n const buildTags = new Set<string>();\n\n const tree = parser.parse(content);\n const cursor = tree.cursor();\n\n do {\n switch (cursor.name) {\n case \"LineComment\": {\n const text = content.slice(cursor.from, cursor.to);\n const tagM = text.match(TAG_RE);\n if (tagM?.[1]) tags.add(tagM[1]);\n\n const newBuild = text.match(BUILD_NEW_RE);\n if (newBuild) extractBuildTokens(newBuild[1] as string, buildTags);\n\n const oldBuild = text.match(BUILD_OLD_RE);\n if (oldBuild) extractBuildTokens(oldBuild[1] as string, buildTags);\n break;\n }\n\n case \"ImportSpec\": {\n // ImportSpec: DefName? String\n // The String child always holds the quoted import path.\n const stringNode = cursor.node.getChild(\"String\");\n if (stringNode) {\n const raw = content.slice(stringNode.from, stringNode.to);\n // Strip surrounding double-quotes\n const specifier = raw.slice(1, -1);\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isExternal: true,\n isStyle: false,\n type: \"static\",\n });\n }\n break;\n }\n\n case \"FunctionDecl\":\n case \"TypeDecl\":\n case \"VarDecl\":\n case \"ConstDecl\": {\n // For FunctionDecl the DefName is a direct child.\n // For TypeDecl the DefName lives inside TypeSpec.\n // For VarDecl/ConstDecl the DefName lives inside VarSpec/ConstSpec.\n const nameNode =\n cursor.node.getChild(\"DefName\") ??\n cursor.node.getChild(\"TypeSpec\")?.getChild(\"DefName\") ??\n cursor.node.getChild(\"VarSpec\")?.getChild(\"DefName\") ??\n cursor.node.getChild(\"ConstSpec\")?.getChild(\"DefName\");\n\n if (nameNode) {\n const name = content.slice(nameNode.from, nameNode.to);\n // Go export rule: identifier starts with an uppercase letter\n if (name !== \"_\" && /^[A-Z]/.test(name) && !exportMap.has(name)) {\n exportMap.set(name, { name });\n }\n }\n break;\n }\n }\n } while (cursor.next());\n\n const importsTestingPkg = imports.some((importEdge) => importEdge.rawSpecifier === \"testing\");\n const category =\n path.basename(filePath).endsWith(\"_test.go\") || tags.has(\"test\") || importsTestingPkg\n ? \"test\"\n : \"logic\";\n\n const allTagNames = new Set([...tags, ...buildTags]);\n return {\n imports,\n exports: Array.from(exportMap.values()),\n tags: Array.from(allTagNames).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n\n/**\n * @description Extracts individual identifier tokens from a Go build constraint expression.\n * Splits on operators and punctuation, strips leading `!`, discards the pseudo-tag `ignore`.\n * @param {string} expr - Raw expression text after `//go:build` or `// +build`.\n * @param {Set<string>} out - Set to populate with extracted tag names.\n */\nfunction extractBuildTokens(expr: string, out: Set<string>): void {\n for (const tok of expr.split(/[\\s,&|!()]+/)) {\n const name = tok.trim();\n if (name && name !== \"ignore\") out.add(name);\n }\n}\n","/** Parses LiveScript files to extract import edges and tag annotations. */\n// @ts-expect-error\nimport ls from \"livescript\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { stripQuotes } from \"../utils\";\n\ninterface LiveScriptNode {\n constructor: { name: string };\n type?: string;\n right?: { value: string };\n head?: { value: string };\n tails?: Array<{\n constructor: { name: string };\n type?: string;\n args?: Array<{ value: string }>;\n }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Scans raw source text for `@tag` comment markers and collects the tag\n * names they carry. Runs before AST parsing so tags are available for classification.\n * @param content - Raw source text of the LiveScript file.\n * @returns A set of tag name strings found in `@tag` annotations.\n */\nfunction extractTags(content: string): Set<string> {\n const tags = new Set<string>();\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let match = tagRegex.exec(content);\n while (match !== null) {\n if (match[1]) tags.add(match[1]);\n match = tagRegex.exec(content);\n }\n return tags;\n}\n\n/**\n * @description Determines whether a file is a test or production-logic file by checking\n * path conventions (.test., .spec.) and the presence of an explicit `@tag test` annotation.\n * @param filePath - Absolute or relative path to the file being classified.\n * @param tags - Tag names extracted from the file's comments.\n * @returns \"test\" if the file is identified as a test file, \"logic\" otherwise.\n */\nfunction classifyFile(filePath: string, tags: Set<string>): \"test\" | \"logic\" {\n const lower = filePath.toLowerCase();\n return lower.includes(\".test.\") || lower.includes(\".spec.\") || tags.has(\"test\")\n ? \"test\"\n : \"logic\";\n}\n\nconst POSITIONAL_KEYS = new Set([\n \"first_line\",\n \"first_column\",\n \"last_line\",\n \"last_column\",\n \"line\",\n \"column\",\n]);\n\n/**\n * @description Inspects a single AST node and returns an ImportEdge if the node represents\n * an `import` statement or a `require()` call, or null if it is neither.\n * @param node - The AST node to inspect.\n * @param filePath - Source path to stamp onto any emitted edge.\n * @returns An ImportEdge for the detected dependency, or null if the node is not an import.\n */\nfunction extractEdge(node: LiveScriptNode, filePath: string): ImportEdge | null {\n const type = node.constructor?.name || node.type;\n\n if (type === \"Import\") {\n const raw = node.right?.value;\n if (typeof raw === \"string\") {\n const specifier = stripQuotes(raw);\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"static\",\n };\n }\n }\n\n if (type === \"Chain\" && node.head?.value === \"require\") {\n const call = node.tails?.[0];\n if (call?.constructor?.name === \"Call\" || call?.type === \"Call\") {\n const raw = call.args?.[0]?.value;\n if (typeof raw === \"string\") {\n const specifier = stripQuotes(raw);\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * @description Recursively walks a LiveScript AST and collects all import edges found\n * within the tree. Skips positional metadata keys to avoid infinite recursion.\n * @param node - The root AST node to walk.\n * @param filePath - Source path forwarded to each discovered edge.\n * @returns All ImportEdge values found in this node and its descendants.\n */\nfunction collectEdges(node: LiveScriptNode, filePath: string): ImportEdge[] {\n if (!node || typeof node !== \"object\") return [];\n\n const edges: ImportEdge[] = [];\n const edge = extractEdge(node, filePath);\n if (edge) edges.push(edge);\n\n for (const key in node) {\n if (POSITIONAL_KEYS.has(key)) continue;\n const child = node[key];\n if (!child || typeof child !== \"object\") continue;\n if (Array.isArray(child)) {\n for (const c of child) edges.push(...collectEdges(c as LiveScriptNode, filePath));\n } else {\n edges.push(...collectEdges(child as LiveScriptNode, filePath));\n }\n }\n\n return edges;\n}\n\n/**\n * @description Parses a LiveScript source file to extract its dependency edges, comment-marker\n * tags, and file category. Handles both ES-style `import` statements and CommonJS `require()`\n * calls. Falls back gracefully if the LiveScript AST cannot be produced.\n * @param filePath - Path used as the source identifier on all emitted import edges.\n * @param content - Raw LiveScript source text to parse.\n * @returns A ParseResult with collected imports, an empty exports list, extracted tags, and the file category.\n */\nexport function parseLiveScript(filePath: string, content: string): ParseResult {\n const tags = extractTags(content);\n const category = classifyFile(filePath, tags);\n let imports: ImportEdge[] = [];\n\n try {\n imports = collectEdges(ls.ast(content) as LiveScriptNode, filePath);\n } catch (_e) {\n // ignore parse errors\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/**\n * @description Removes a single surrounding quote pair (`'` or `\"`) from a string if one\n * is present. Safe to call on already-unquoted values — returns the input unchanged.\n * @param value - The string to unquote.\n * @returns The unquoted string, or the original value if it was not quoted.\n */\nexport function stripQuotes(value: string): string {\n return value.startsWith(\"'\") || value.startsWith('\"') ? value.slice(1, -1) : value;\n}\n","/** Parses Lua source files via luaparse to extract require() dependency edges and @tag annotations. */\nimport type { Chunk, Node } from \"luaparse\";\nimport luaparse from \"luaparse\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { stripQuotes } from \"../utils\";\n\n/**\n * @description Extracts `@tag <name>` comment annotations from raw Lua source text.\n * @param {string} content - Raw Lua source text.\n * @returns {Set<string>} The set of distinct tag names found in `content`.\n */\nfunction extractTagAnnotations(content: string): Set<string> {\n const tagNames = new Set<string>();\n const tagAnnotationRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let annotationMatch = tagAnnotationRegex.exec(content);\n while (annotationMatch !== null) {\n if (annotationMatch[1]) tagNames.add(annotationMatch[1]);\n annotationMatch = tagAnnotationRegex.exec(content);\n }\n return tagNames;\n}\n\n/**\n * @description Classifies a Lua file as `\"test\"` or `\"logic\"` based on its filename\n * (`.test.` / `.spec.` substrings) or the presence of an explicit `@tag test` annotation.\n * @param {string} filePath - Path to the Lua file.\n * @param {Set<string>} tagNames - Tag names already extracted from the file's comments.\n * @returns {\"test\" | \"logic\"} The resolved file category.\n */\nfunction classifyCategory(filePath: string, tagNames: Set<string>): \"test\" | \"logic\" {\n const lowerCasePath = filePath.toLowerCase();\n const isTest =\n lowerCasePath.includes(\".test.\") || lowerCasePath.includes(\".spec.\") || tagNames.has(\"test\");\n return isTest ? \"test\" : \"logic\";\n}\n\n/**\n * @description Recursively walks a luaparse AST and returns a `require()` dependency edge for\n * every matching call expression found. Skips `loc` keys to avoid processing location\n * metadata objects.\n * @param {Chunk} ast - The parsed luaparse AST root.\n * @param {string} filePath - Path to the Lua file; used as `fromPath` on emitted edges.\n * @returns {ImportEdge[]} One edge per `require()` call found with a string-literal argument.\n */\nfunction collectRequireEdges(ast: Chunk, filePath: string): ImportEdge[] {\n const importEdges: ImportEdge[] = [];\n\n function visitNode(node: Node) {\n if (!node || typeof node !== \"object\") return;\n\n if (\n (node.type === \"CallExpression\" || node.type === \"StringCallExpression\") &&\n node.base?.type === \"Identifier\" &&\n node.base?.name === \"require\"\n ) {\n let specifier: string | undefined;\n if (node.type === \"CallExpression\") {\n const requireArgument = node.arguments?.[0];\n if (requireArgument?.type === \"StringLiteral\") {\n // raw is like \"'module'\" or '\"module\"'\n specifier = stripQuotes(requireArgument.raw);\n }\n } else if (node.type === \"StringCallExpression\") {\n const requireArgument = node.argument;\n if (requireArgument?.type === \"StringLiteral\") {\n specifier = stripQuotes(requireArgument.raw);\n }\n }\n\n if (specifier) {\n importEdges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n });\n }\n }\n\n for (const key in node) {\n if (key === \"loc\") continue;\n const childValue = (node as unknown as Record<string, unknown>)[key];\n if (childValue && typeof childValue === \"object\") {\n if (Array.isArray(childValue)) {\n for (const childNode of childValue) visitNode(childNode as Node);\n } else {\n visitNode(childValue as Node);\n }\n }\n }\n }\n\n visitNode(ast);\n return importEdges;\n}\n\n/**\n * @description Parses a Lua source file using luaparse to extract `require()` dependency edges\n * and `@tag` comment annotations. Falls back to an empty import list if the file contains\n * syntax errors.\n * @param filePath - Path to the Lua file; used as the source on emitted edges and for test-file classification.\n * @param content - Raw Lua source text.\n * @returns Parsed imports, empty exports list, extracted tags, and resolved category.\n */\nexport function parseLua(filePath: string, content: string): ParseResult {\n const tagNames = extractTagAnnotations(content);\n const category = classifyCategory(filePath, tagNames);\n\n let imports: ImportEdge[] = [];\n try {\n const ast: Chunk = luaparse.parse(content);\n imports = collectRequireEdges(ast, filePath);\n } catch (_parseError) {\n // Ignore parse errors\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tagNames).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Python source files using the Lezer parser to extract import edges, exports, and tag annotations. */\nimport path from \"node:path\";\nimport type { SyntaxNode } from \"@lezer/common\";\nimport { parser } from \"@lezer/python\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\nconst TEST_LIBS = new Set([\"pytest\", \"unittest\", \"nose\", \"hypothesis\"]);\n\n/**\n * @description Parses a Python source file using the Lezer parser to extract import edges,\n * top-level definitions as exports, `# @tag` comment markers, and file category.\n * @param {string} filePath - Path to the `.py` file; used for test-file classification by basename convention.\n * @param {string} content - Raw Python source text.\n * @returns {ParseResult} Parsed imports, top-level exports, comment-marker tags, and resolved category.\n */\nexport function parsePython(filePath: string, content: string): ParseResult {\n const imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n const tags = new Set<string>();\n const baseName = path.basename(filePath).toLowerCase();\n\n const tree = parser.parse(content);\n const cursor = tree.cursor();\n\n do {\n switch (cursor.name) {\n case \"Comment\": {\n const tagMatch = content.slice(cursor.from, cursor.to).match(/#\\s*@tag\\s+([a-zA-Z0-9_-]+)/);\n if (tagMatch?.[1]) tags.add(tagMatch[1]);\n break;\n }\n case \"ImportStatement\": {\n for (const edge of extractImportEdges(cursor.node, content, filePath)) {\n imports.push(edge);\n }\n break;\n }\n case \"FunctionDefinition\":\n case \"ClassDefinition\": {\n // Only top-level — parent must be Script or a DecoratedStatement directly under Script\n const parentNode = cursor.node.parent;\n const isTopLevel =\n parentNode?.name === \"Script\" ||\n (parentNode?.name === \"DecoratedStatement\" && parentNode.parent?.name === \"Script\");\n if (isTopLevel) {\n const nameNode = cursor.node.getChild(\"VariableName\");\n if (nameNode) exports.push({ name: content.slice(nameNode.from, nameNode.to) });\n }\n break;\n }\n\n case \"AssignStatement\": {\n // Only top-level simple assignments: `MY_VAR = value`\n if (cursor.node.parent?.name === \"Script\") {\n const target = cursor.node.firstChild;\n if (target?.name === \"VariableName\") {\n exports.push({ name: content.slice(target.from, target.to) });\n }\n }\n break;\n }\n }\n } while (cursor.next());\n\n const category = resolveCategory(baseName, imports, tags);\n if (category === \"test\") tags.add(\"test\");\n\n return {\n imports,\n exports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n\n// ─── import edge extraction ───────────────────────────────────────────────────\n\n/**\n * @description Dispatches a single Lezer `ImportStatement` node to the appropriate extractor\n * based on whether it begins with `from` (from-import form) or not (bare import form).\n * @param {SyntaxNode} node - The `ImportStatement` AST node to process.\n * @param {string} src - Full source text, used to slice node ranges into strings.\n * @param {string} filePath - Source file path stamped onto each emitted edge.\n * @returns {ImportEdge[]} One or more import edges extracted from the statement.\n */\nfunction extractImportEdges(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const first = node.firstChild;\n if (!first) return [];\n return first.name === \"from\"\n ? extractFromImport(node, src, filePath)\n : extractBareImport(node, src, filePath);\n}\n\n/**\n * Handles `from <module> import <names>` in all forms:\n * absolute, relative (. / .. / ...), dotted module paths, star, aliases.\n */\nfunction extractFromImport(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const fromKw = node.firstChild;\n if (!fromKw) return [];\n\n // Find the `import` keyword that splits module from names\n let importKw: SyntaxNode | null = fromKw.nextSibling;\n while (importKw && importKw.name !== \"import\") importKw = importKw.nextSibling;\n if (!importKw) return [];\n\n // Raw module text: everything between `from` end and `import` start.\n // e.g. \" .models\", \" os.path\", \" .. \", \" ...core.utils\"\n const rawModule = src.slice(fromKw.to, importKw.from).trim();\n const importedNames = collectImportedNames(importKw.nextSibling, src);\n if (!importedNames.length) return [];\n\n // Split leading dots from the rest of the module path\n let dotCount = 0;\n while (dotCount < rawModule.length && rawModule[dotCount] === \".\") dotCount++;\n const modulePart = rawModule.slice(dotCount); // e.g. \"models\", \"core.utils\", \"\"\n\n if (dotCount === 0) {\n // Absolute import: `from pathlib import Path`\n // Keep dotted module name as-is; resolver converts dots → path separators.\n return [makeEdge(filePath, rawModule, importedNames, true)];\n }\n\n // n=1 → \"./\" (current package)\n // n=2 → \"../\" (parent package)\n // n=3 → \"../../\" (grandparent)\n const prefix = dotCount === 1 ? \"./\" : \"../\".repeat(dotCount - 1);\n\n if (!modulePart) {\n // `from . import utils, models` — each name is its own sub-module.\n // `from . import *` — edge to the package init.\n if (importedNames[0] === \"*\") {\n return [makeEdge(filePath, prefix.slice(0, -1), [\"*\"], false)];\n }\n return importedNames.map((name) => makeEdge(filePath, prefix + name, [name], false));\n }\n\n // `from .models import User` → \"./models\"\n // `from .models.user import X` → \"./models/user\"\n return [makeEdge(filePath, prefix + modulePart.replace(/\\./g, \"/\"), importedNames, false)];\n}\n\n/**\n * Handles `import <module>` statements, including dotted paths and aliases.\n * `import os, sys` produces two edges; `import os.path as p` uses the original module name.\n */\nfunction extractBareImport(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const edges: ImportEdge[] = [];\n let childNode: SyntaxNode | null = node.firstChild?.nextSibling ?? null; // skip \"import\" keyword\n\n while (childNode) {\n if (childNode.name === \"VariableName\") {\n // Collect possibly dotted module name: os + . + path → \"os.path\"\n let modName = src.slice(childNode.from, childNode.to);\n while (\n childNode.nextSibling?.name === \".\" &&\n childNode.nextSibling.nextSibling?.name === \"VariableName\"\n ) {\n childNode = childNode.nextSibling.nextSibling as SyntaxNode;\n modName += `.${src.slice(childNode.from, childNode.to)}`;\n }\n // Skip optional `as alias`\n if (childNode.nextSibling?.name === \"as\") {\n childNode = childNode.nextSibling.nextSibling ?? childNode.nextSibling;\n }\n edges.push(makeEdge(filePath, modName, [\"*\"], true));\n }\n childNode = childNode.nextSibling;\n }\n\n return edges;\n}\n\n/**\n * Walks the sibling chain after `import`, collecting symbol names and skipping `as` aliases.\n */\nfunction collectImportedNames(start: SyntaxNode | null, src: string): string[] {\n const names: string[] = [];\n let childNode: SyntaxNode | null = start;\n while (childNode) {\n if (childNode.name === \"*\") {\n names.push(\"*\");\n } else if (childNode.name === \"VariableName\") {\n names.push(src.slice(childNode.from, childNode.to));\n // Skip `as alias` if present\n if (childNode.nextSibling?.name === \"as\") {\n childNode = childNode.nextSibling.nextSibling ?? childNode.nextSibling;\n }\n }\n childNode = childNode.nextSibling;\n }\n return names;\n}\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeEdge(\n filePath: string,\n rawSpecifier: string,\n symbols: string[],\n isExternal: boolean,\n): ImportEdge {\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier,\n isStyle: false,\n isExternal,\n type: \"static\",\n symbols: symbols.length > 0 ? symbols : undefined,\n };\n}\n\n/**\n * @description Classifies a Python file as `\"test\"`, `\"config\"`, or `\"logic\"` based on\n * its basename convention, imports from known test libraries, and explicit `@tag test` markers.\n * @param {string} baseName - Lowercase basename of the file, e.g. `\"test_auth.py\"`.\n * @param {ImportEdge[]} imports - Resolved import edges used to detect test-library usage.\n * @param {Set<string>} tags - Tag names extracted from comments.\n * @returns {\"test\" | \"config\" | \"logic\"} The resolved category for this file.\n */\nfunction resolveCategory(\n baseName: string,\n imports: ImportEdge[],\n tags: Set<string>,\n): \"test\" | \"config\" | \"logic\" {\n if (baseName.startsWith(\"test_\") || baseName.endsWith(\"_test.py\")) return \"test\";\n if (baseName === \"conftest.py\" || baseName === \"setup.py\") return \"config\";\n if (tags.has(\"test\")) return \"test\";\n if (imports.some((imp) => TEST_LIBS.has(imp.rawSpecifier))) return \"test\";\n return \"logic\";\n}\n","/** Parses JavaScript and TypeScript source files using the TypeScript Compiler API to extract imports, exports, tags, category, and complexity. */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport type {\n ExportedSymbol,\n FunctionComplexity,\n ImportEdge,\n StructuredTag,\n} from \"../../types/node\";\nimport type { FileType, ImportType, NodeCategory } from \"../../types/parse\";\nimport { getBarrelThreshold, getTestLibraries, getTestPatterns, isConfigFile } from \"../classify\";\nimport { computeComplexity } from \"../complexity\";\nimport { isStyleFile } from \"../file-type\";\nimport { handleTagging } from \"../tagging\";\nimport type { ParseContext, ParseResult, RawCallEdge } from \"../types\";\n\n/**\n * @description Parses a JavaScript or TypeScript file using the TypeScript Compiler API.\n *\n * Creates a source file AST, walks every node to collect imports, exports, tags, and\n * category hints, then classifies the file and returns a structured result.\n * @param filePath - Absolute path of the file; used as the node identifier in the graph.\n * @param content - Raw source content of the file.\n * @param fileType - Determines the TS script kind (`TSX` for TypeScript, `JSX` for JavaScript).\n * @returns Parsed result containing imports, exports, tags, and category.\n */\nexport function parseCodeFile(filePath: string, content: string, fileType: FileType): ParseResult {\n const imports: ImportEdge[] = [];\n const exports: Map<string, ExportedSymbol> = new Map();\n const tags: Set<StructuredTag> = new Set();\n\n const sourceFile = ts.createSourceFile(\n filePath,\n content,\n ts.ScriptTarget.Latest,\n true,\n fileType === \"typescript\" ? ts.ScriptKind.TSX : ts.ScriptKind.JSX,\n );\n\n const context: ParseContext = {\n filePath,\n imports,\n exports,\n tags,\n rawCallEdges: [],\n sourceFile,\n hasUI: false,\n hasTypesOnly: true,\n totalStatements: 0,\n exportStatements: 0,\n };\n\n const visit = (node: ts.Node) => {\n analyzeNode(node, context);\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n const category = determineCategory(filePath, context);\n if (category === \"test\" || category === \"barrel\") {\n tags.add({ name: category, kind: \"comment-marker\" });\n }\n\n if (category !== \"test\") {\n collectRawCallEdges(context, sourceFile);\n }\n\n const firstStatement = sourceFile.statements[0];\n const description = firstStatement ? extractJsDoc(firstStatement) : undefined;\n const { complexity, cognitiveComplexity } = computeComplexity(sourceFile);\n const functions = collectFunctionComplexity(sourceFile);\n\n return {\n imports,\n exports: Array.from(exports.values()),\n tags: Array.from(tags),\n category,\n rawCallEdges: context.rawCallEdges ?? [],\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\n ...(description !== undefined ? { description } : {}),\n };\n}\n\n/**\n * @description Walks the entire source file and records per-function complexity for every named\n * function-like declaration: function declarations, const-assigned arrow/function expressions,\n * and class methods/constructors/accessors (named `ClassName.member`). Anonymous inline\n * callbacks are skipped since they have no stable name to key results on.\n * @param sourceFile - The TypeScript source file AST to walk.\n * @returns Per-function complexity entries, in traversal order.\n */\nfunction collectFunctionComplexity(sourceFile: ts.SourceFile): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n\n const record = (name: string, node: ts.Node): void => {\n const { complexity, cognitiveComplexity } = computeComplexity(node);\n const line = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;\n results.push({ name, line, complexity, cognitiveComplexity });\n };\n\n const visit = (node: ts.Node, className: string | undefined): void => {\n if (ts.isFunctionDeclaration(node) && node.name && node.body) {\n record(node.name.text, node);\n } else if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))\n ) {\n record(node.name.text, node.initializer);\n } else if (\n className &&\n ts.isMethodDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.${node.name.text}`, node);\n } else if (className && ts.isConstructorDeclaration(node) && node.body) {\n record(`${className}.constructor`, node);\n } else if (\n className &&\n ts.isGetAccessorDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.get ${node.name.text}`, node);\n } else if (\n className &&\n ts.isSetAccessorDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.set ${node.name.text}`, node);\n }\n\n if (ts.isClassDeclaration(node) && node.name) {\n const nextClassName = node.name.text;\n ts.forEachChild(node, (child) => visit(child, nextClassName));\n return;\n }\n ts.forEachChild(node, (child) => visit(child, className));\n };\n\n visit(sourceFile, undefined);\n return results;\n}\n\n/**\n * @description Constructs an `ExportedSymbol` from an AST declaration node, attaching JSDoc, flags, and type signature where available.\n * @param name - The exported symbol name.\n * @param declNode - The specific declaration node (e.g. function or variable declarator) used for flags and signature extraction.\n * @param stmtNode - The parent statement node used for JSDoc extraction.\n * @param sourceFile - The source file, required by the TS printer for signature serialisation.\n * @returns The fully populated `ExportedSymbol`.\n */\nfunction makeExportedSymbol(\n name: string,\n declNode: ts.Node,\n stmtNode: ts.Node,\n sourceFile: ts.SourceFile,\n): ExportedSymbol {\n const sym: ExportedSymbol = { name };\n const doc = extractJsDoc(stmtNode);\n if (doc !== undefined) sym.doc = doc;\n const flags = extractJsDocFlags(declNode);\n if (flags !== undefined) sym.flags = flags;\n const sig = extractSignature(declNode, sourceFile);\n if (sig !== undefined) sym.signature = sig;\n return sym;\n}\n\n/**\n * @description Extracts the text of the first JSDoc comment block attached to a node.\n * @param node - The AST node to inspect.\n * @returns The comment text, or `undefined` if no JSDoc is present.\n */\nfunction extractJsDoc(node: ts.Node): string | undefined {\n const cmts = ts.getJSDocCommentsAndTags(node);\n for (const cmtNode of cmts) {\n if (ts.isJSDoc(cmtNode) && cmtNode.comment) {\n return ts.getTextOfJSDocComment(cmtNode.comment) || undefined;\n }\n }\n return undefined;\n}\n\n/**\n * @description Extracts known JSDoc tag names from a node.\n *\n * Only a fixed set of tags is recognised: `deprecated`, `internal`, `public`, `alpha`, `beta`.\n * Unknown tags are ignored so that project-specific markers don't pollute the symbol metadata.\n * @param node - The AST node to inspect.\n * @returns Array of matched tag names, or `undefined` if none are present.\n */\nfunction extractJsDocFlags(node: ts.Node): string[] | undefined {\n const KNOWN = new Set([\"deprecated\", \"internal\", \"public\", \"alpha\", \"beta\"]);\n const flags = ts\n .getJSDocTags(node)\n .map((jsDocTag) => jsDocTag.tagName.text)\n .filter((name) => KNOWN.has(name));\n return flags.length > 0 ? flags : undefined;\n}\n\n/**\n * @description Serialises the type signature of a declaration node into a human-readable string.\n *\n * Covers functions, methods, variable declarations (including arrow functions), classes,\n * interfaces, type aliases, and enums. Returns `undefined` for node kinds with no\n * meaningful signature (e.g. plain object literals).\n * @param node - The declaration node to serialise.\n * @param sourceFile - Required by the TS printer to resolve node text.\n * @returns The signature string, or `undefined` if the node kind is not supported.\n */\nfunction extractSignature(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {\n const printer = ts.createPrinter({ removeComments: true });\n const print = (tsNode: ts.Node) => printer.printNode(ts.EmitHint.Unspecified, tsNode, sourceFile);\n\n if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n const params = node.parameters.map(print).join(\", \");\n const ret = node.type ? print(node.type) : \"void\";\n const tps = node.typeParameters\n ? `<${node.typeParameters.map((tp) => tp.name.text).join(\", \")}>`\n : \"\";\n return `${tps}(${params}) => ${ret}`;\n }\n if (ts.isVariableDeclaration(node)) {\n if (node.type) return print(node.type);\n if (\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))\n ) {\n const fn = node.initializer;\n const params = fn.parameters.map(print).join(\", \");\n const ret = fn.type ? print(fn.type) : \"unknown\";\n return `(${params}) => ${ret}`;\n }\n return undefined;\n }\n if (ts.isClassDeclaration(node) && node.name) return `class ${node.name.text}`;\n if (ts.isInterfaceDeclaration(node)) return `interface ${node.name.text}`;\n if (ts.isTypeAliasDeclaration(node)) return print(node.type);\n if (ts.isEnumDeclaration(node)) return `enum ${node.name.text}`;\n return undefined;\n}\n\n/**\n * @description Dispatches a single AST node to all analysis handlers that update the parse context.\n * @param node - The current AST node being visited.\n * @param ctx - The shared parse context accumulating imports, exports, tags, and category hints.\n */\nfunction analyzeNode(node: ts.Node, ctx: ParseContext) {\n updateStatementCounts(node, ctx);\n updateCategoryHints(node, ctx);\n handleImports(node, ctx);\n handleExports(node, ctx);\n handleCalls(node, ctx);\n handleTagging(node, ctx);\n}\n\n/**\n * @description Counts total and export statements in a source file and writes the totals to the parse context.\n *\n * Only runs for `SourceFile` nodes — all other node kinds are ignored.\n * The counts are later used by `determineCategory` to detect barrel files.\n * @param node - The current AST node; only `SourceFile` nodes are processed.\n * @param ctx - The parse context to update.\n */\nfunction updateStatementCounts(node: ts.Node, ctx: ParseContext) {\n if (!ts.isSourceFile(node)) return;\n const statements = node.statements.filter((statement) => !ts.isEmptyStatement(statement));\n ctx.totalStatements = statements.length;\n ctx.exportStatements = statements.filter(\n (statement) =>\n ts.isExportDeclaration(statement) ||\n ts.isExportAssignment(statement) ||\n hasExportModifier(statement),\n ).length;\n}\n\n/**\n * @description Updates `hasUI` and `hasTypesOnly` flags on the context based on the current node kind.\n *\n * JSX nodes set `hasUI`; function/class/variable/enum nodes clear `hasTypesOnly`.\n * Non-type-only `ExportDeclaration` nodes (re-exports of values) also clear `hasTypesOnly`.\n * Both flags feed into `determineCategory` after the full AST walk.\n * @param node - The current AST node.\n * @param ctx - The parse context whose flags are mutated.\n */\nfunction updateCategoryHints(node: ts.Node, ctx: ParseContext) {\n if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) {\n ctx.hasUI = true;\n ctx.hasTypesOnly = false;\n return;\n }\n if (\n ts.isFunctionDeclaration(node) ||\n ts.isMethodDeclaration(node) ||\n ts.isArrowFunction(node) ||\n ts.isClassDeclaration(node) ||\n ts.isVariableStatement(node) ||\n ts.isEnumDeclaration(node)\n ) {\n ctx.hasTypesOnly = false;\n return;\n }\n if (ts.isExportDeclaration(node) && !isTypeOnlyExportDecl(node)) {\n ctx.hasTypesOnly = false;\n }\n}\n\n/**\n * @description Returns true if an export declaration exports only type-level bindings.\n *\n * Covers `export type { ... }` (declaration-level) and `export { type Foo, type Bar }`\n * (element-level, TypeScript 4.5+). Star re-exports without `type` are treated as value\n * exports because their symbol kind is not statically knowable.\n */\nfunction isTypeOnlyExportDecl(node: ts.ExportDeclaration): boolean {\n if (node.isTypeOnly) return true;\n if (!node.exportClause || !ts.isNamedExports(node.exportClause)) return false;\n return node.exportClause.elements.every((el) => el.isTypeOnly);\n}\n\n/**\n * @description Handles a static `import` declaration and pushes an `ImportEdge` onto the context.\n *\n * Symbol extraction distinguishes default imports, named imports, and namespace imports (`* as ns`).\n * A declaration with no import clause (side-effect import) produces an edge with no symbols.\n * @param node - The current AST node; only `ImportDeclaration` nodes are processed.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleImports(node: ts.Node, ctx: ParseContext) {\n if (!ts.isImportDeclaration(node)) return;\n if (!node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) return;\n\n const symbols: string[] = [];\n if (node.importClause) {\n if (node.importClause.name) symbols.push(\"default\");\n if (node.importClause.namedBindings) {\n if (ts.isNamedImports(node.importClause.namedBindings)) {\n for (const element of node.importClause.namedBindings.elements) {\n symbols.push(element.name.text);\n }\n } else if (ts.isNamespaceImport(node.importClause.namedBindings)) {\n symbols.push(\"*\");\n }\n }\n }\n\n const type: ImportType = symbols.length > 0 ? \"static\" : \"side-effect\";\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: node.moduleSpecifier.text,\n isStyle: isStyleFile(node.moduleSpecifier.text),\n type,\n symbols: symbols.length > 0 ? symbols : undefined,\n });\n}\n\n/**\n * @description Visits an AST node and records any exports it declares into `ctx`.\n *\n * Handles three syntactic forms:\n *\n * 1. **Re-export with source** (`export { A, B } from './mod'` / `export * from './mod'`):\n * Adds an `ImportEdge` of type `\"re-export\"` so the graph captures the cross-file\n * relationship. Symbols are the named exports, or `[\"*\"]` for a star re-export.\n *\n * 2. **Local re-export** (`export { localName }`):\n * Registers the symbol in `ctx.exports`; no import edge is created because no\n * external module is referenced.\n *\n * 3. **Inline export modifier** (`export function foo`, `export const bar`, `export default`):\n * Registers the exported name (or `\"default\"` for `export default`) in `ctx.exports`.\n * @param node - The current AST node to inspect.\n * @param ctx - The parse context whose `exports` and `imports` are updated.\n */\nfunction handleExports(node: ts.Node, ctx: ParseContext) {\n if (ts.isExportDeclaration(node)) {\n handleExportDeclaration(node, ctx);\n } else if (ts.isExportAssignment(node)) {\n ctx.exports.set(\"default\", { name: \"default\" });\n } else if (hasExportModifier(node)) {\n handleInlineExport(node, ctx);\n }\n}\n\n/**\n * @description Handles `export { ... }` and `export { ... } from '...'` / `export * from '...'`.\n *\n * When a module specifier is present this is a re-export edge; otherwise it is a\n * local symbol registration.\n * @param node - The export declaration node.\n * @param ctx - The parse context to update.\n */\nfunction handleExportDeclaration(node: ts.ExportDeclaration, ctx: ParseContext) {\n if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {\n handleReExport(node, node.moduleSpecifier.text, ctx);\n } else if (node.exportClause && ts.isNamedExports(node.exportClause)) {\n for (const element of node.exportClause.elements) {\n const name = element.name.text;\n ctx.exports.set(name, { name });\n }\n }\n}\n\n/**\n * @description Records a cross-module re-export as an `ImportEdge`.\n *\n * Extracts named symbols from `export { A } from '...'`, or uses `\"*\"` for\n * `export * from '...'` (no export clause).\n * @param node - The export declaration node.\n * @param specifier - The raw module specifier string from the source.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleReExport(node: ts.ExportDeclaration, specifier: string, ctx: ParseContext) {\n const symbols = extractReExportSymbols(node);\n const edge: ImportEdge = {\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"re-export\",\n };\n if (symbols.length > 0) edge.symbols = symbols;\n ctx.imports.push(edge);\n}\n\n/**\n * @description Returns the exported symbol names from a re-export declaration.\n *\n * Returns `[\"*\"]` when the export clause is absent (star re-export), or an empty\n * array for namespace re-exports (`export * as ns from '...'`) which are not yet tracked.\n * @param node - The export declaration node to inspect.\n * @returns Array of symbol names, or `[\"*\"]` for a star re-export.\n */\nfunction extractReExportSymbols(node: ts.ExportDeclaration): string[] {\n if (!node.exportClause) return [\"*\"];\n if (ts.isNamedExports(node.exportClause)) {\n return node.exportClause.elements.map((el) => el.name.text);\n }\n return [];\n}\n\n/**\n * @description Handles declarations that carry an `export` modifier, e.g.:\n * `export function foo`, `export class Bar`, `export const baz`, `export type T`.\n *\n * Registers each exported name into `ctx.exports` with its signature and JSDoc metadata.\n * @param node - The exported declaration node.\n * @param ctx - The parse context whose `exports` map is updated.\n */\nfunction handleInlineExport(node: ts.Node, ctx: ParseContext) {\n const isNamedDeclaration =\n ts.isFunctionDeclaration(node) ||\n ts.isClassDeclaration(node) ||\n ts.isInterfaceDeclaration(node) ||\n ts.isTypeAliasDeclaration(node) ||\n ts.isEnumDeclaration(node);\n\n if (isNamedDeclaration && node.name) {\n const name = node.name.text;\n ctx.exports.set(name, makeExportedSymbol(name, node, node, ctx.sourceFile));\n return;\n }\n\n if (ts.isVariableStatement(node)) {\n for (const decl of node.declarationList.declarations) {\n if (ts.isIdentifier(decl.name)) {\n const name = decl.name.text;\n ctx.exports.set(name, makeExportedSymbol(name, decl, node, ctx.sourceFile));\n }\n }\n }\n}\n\n/**\n * @description Handles dynamic `import()` calls and `require()` calls, pushing import edges onto the context.\n *\n * The string argument is hoisted before the call-type check so both branches share the\n * same guard, removing a level of nesting. Non-string (computed) specifiers are silently ignored.\n * @param node - The current AST node; only `CallExpression` nodes are processed.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleCalls(node: ts.Node, ctx: ParseContext) {\n if (!ts.isCallExpression(node)) return;\n\n const arg = node.arguments[0];\n if (!arg || !ts.isStringLiteral(arg)) return;\n\n if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: arg.text,\n isStyle: isStyleFile(arg.text),\n type: \"dynamic\",\n });\n } else if (ts.isIdentifier(node.expression) && node.expression.text === \"require\") {\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: arg.text,\n isStyle: isStyleFile(arg.text),\n type: \"require\",\n });\n }\n}\n\n/**\n * @description Classifies a parsed file into a `NodeCategory` based on file name patterns, imports, and AST shape.\n *\n * Checks are ordered from most to least specific: explicit test files, config files,\n * testing-library imports, JSX/UI presence, barrel ratio, type-only content, and finally\n * the default `\"logic\"` bucket.\n * @param filePath - The file path, checked against test and config name patterns.\n * @param ctx - The parse context with accumulated category hints from the AST walk.\n * @returns The most specific matching `NodeCategory`.\n */\nfunction determineCategory(filePath: string, ctx: ParseContext): NodeCategory {\n const baseName = path.basename(filePath).toLowerCase();\n const ext = path.extname(filePath).toLowerCase();\n\n // 1. Explicit test files\n if (getTestPatterns().some((pattern) => baseName.includes(pattern))) {\n return \"test\";\n }\n\n // 2. Configuration files (built-in list + user-registered matchers)\n if (isConfigFile(baseName)) {\n return \"config\";\n }\n\n // 3. UI detection (JSX/TSX or explicit UI elements or testing library imports)\n const importsTestingLib = ctx.imports.some((imp) =>\n getTestLibraries().some((lib) => imp.rawSpecifier.includes(lib)),\n );\n\n if (importsTestingLib) return \"test\";\n\n if (ext === \".tsx\" || ext === \".jsx\" || ctx.hasUI) return \"ui\";\n\n // 4. Type-only files (interfaces, types, type-only re-exports)\n if (ctx.hasTypesOnly && ctx.totalStatements > 0) return \"type-only\";\n\n // 5. Barrel files (mostly value exports/re-exports)\n if (ctx.totalStatements > 0 && ctx.exportStatements / ctx.totalStatements > getBarrelThreshold())\n return \"barrel\";\n\n return \"logic\";\n}\n\n/**\n * @description Checks whether a node has an `export` keyword modifier.\n * @param node - The AST node to inspect.\n * @returns `true` if the node carries an `export` modifier.\n */\nfunction hasExportModifier(node: ts.Node): boolean {\n return (\n ts.canHaveModifiers(node) &&\n ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ===\n true\n );\n}\n\n/**\n * @description Builds a map of imported symbol names to their module specifiers, then walks\n * every top-level exported function body and every class method body to collect\n * caller→callee→specifier triples. Class method edges use `ClassName.methodName` as\n * the `from` field. Populates `ctx.rawCallEdges` in place; skipped entirely for test files.\n * @param {ParseContext} ctx - The parse context whose `rawCallEdges` array is populated.\n * @param {ts.SourceFile} sourceFile - The TypeScript source file AST used to enumerate statements.\n */\nfunction collectRawCallEdges(ctx: ParseContext, sourceFile: ts.SourceFile): void {\n const edges: RawCallEdge[] = ctx.rawCallEdges ?? [];\n ctx.rawCallEdges = edges;\n\n const importSymbolMap = new Map<string, string>();\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;\n const specifier = stmt.moduleSpecifier.text;\n const clause = stmt.importClause;\n if (!clause) continue;\n if (clause.name) importSymbolMap.set(clause.name.text, specifier);\n if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {\n for (const el of clause.namedBindings.elements) {\n importSymbolMap.set(el.name.text, specifier);\n }\n }\n }\n if (importSymbolMap.size === 0) return;\n\n for (const stmt of sourceFile.statements) {\n const fnName = getTopLevelExportedFunctionName(stmt);\n if (fnName) {\n const body = getFunctionBody(stmt);\n if (body) walkCallExpressions(body, fnName, importSymbolMap, edges);\n continue;\n }\n if (ts.isClassDeclaration(stmt) && stmt.name) {\n collectClassMethodCallEdges(stmt, importSymbolMap, edges);\n }\n }\n}\n\n/**\n * @description Walks every method and constructor in a class declaration and records\n * call edges for any imported symbol invocations found in their bodies.\n * Edge `from` fields are formatted as `ClassName.methodName` (or `ClassName.constructor`).\n * @param {ts.ClassDeclaration} classDecl - The class declaration to walk.\n * @param {Map<string, string>} importSymbolMap - Maps local import names to their module specifiers.\n * @param {RawCallEdge[]} edges - Accumulator array that receives discovered edges.\n */\nfunction collectClassMethodCallEdges(\n classDecl: ts.ClassDeclaration,\n importSymbolMap: Map<string, string>,\n edges: RawCallEdge[],\n): void {\n const className = classDecl.name!.text;\n for (const member of classDecl.members) {\n if (ts.isMethodDeclaration(member) && member.body && ts.isIdentifier(member.name)) {\n walkCallExpressions(member.body, `${className}.${member.name.text}`, importSymbolMap, edges);\n } else if (ts.isConstructorDeclaration(member) && member.body) {\n walkCallExpressions(member.body, `${className}.constructor`, importSymbolMap, edges);\n }\n }\n}\n\n/**\n * @description Extracts the name of a top-level exported function from a statement.\n * Recognises both `export function foo` and `export const foo = () => ...` forms.\n * @param stmt - The top-level statement to inspect.\n * @returns The function name, or `undefined` if the statement is not an exported function.\n */\nfunction getTopLevelExportedFunctionName(stmt: ts.Statement): string | undefined {\n if (!hasExportModifier(stmt)) return undefined;\n if (ts.isFunctionDeclaration(stmt) && stmt.name) return stmt.name.text;\n if (ts.isVariableStatement(stmt)) {\n for (const decl of stmt.declarationList.declarations) {\n if (\n ts.isIdentifier(decl.name) &&\n decl.initializer &&\n (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))\n ) {\n return decl.name.text;\n }\n }\n }\n return undefined;\n}\n\n/**\n * @description Extracts the body node from a top-level function declaration or a variable-declared\n * arrow/function expression. Used to scope the call-expression walk to a single function.\n * @param stmt - The top-level statement to inspect.\n * @returns The body node, or `undefined` if the statement is neither a function declaration\n * nor a variable-declared function expression.\n */\nfunction getFunctionBody(stmt: ts.Statement): ts.Node | undefined {\n if (ts.isFunctionDeclaration(stmt)) return stmt.body;\n if (ts.isVariableStatement(stmt)) {\n for (const decl of stmt.declarationList.declarations) {\n if (\n decl.initializer &&\n (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))\n ) {\n return decl.initializer;\n }\n }\n }\n return undefined;\n}\n\n/**\n * @description Recursively walks an AST subtree and records every direct call to an imported\n * symbol as a `RawCallEdge`. Deduplicates so the same (from, to, specifier) triple is only\n * pushed once.\n * @param node - The AST node to walk.\n * @param fnName - The name of the enclosing exported function, used as the `from` field on edges.\n * @param importSymbolMap - Maps local import names to their module specifiers.\n * @param result - Accumulator array that receives discovered edges.\n */\nfunction walkCallExpressions(\n node: ts.Node,\n fnName: string,\n importSymbolMap: Map<string, string>,\n result: RawCallEdge[],\n): void {\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {\n const callee = node.expression.text;\n const specifier = importSymbolMap.get(callee);\n if (\n specifier &&\n !result.some(\n (callEdgeEntry) =>\n callEdgeEntry.from === fnName &&\n callEdgeEntry.to === callee &&\n callEdgeEntry.toSpecifier === specifier,\n )\n ) {\n result.push({ from: fnName, to: callee, toSpecifier: specifier });\n }\n }\n ts.forEachChild(node, (child) => walkCallExpressions(child, fnName, importSymbolMap, result));\n}\n","/** Computes McCabe cyclomatic complexity and cognitive complexity for TypeScript/JavaScript source files. */\nimport ts from \"typescript\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for an AST node: every independent\n * decision point counts (base 1) — `if`, ternary, `for`, `while`, `do`, `switch case`,\n * `catch`, and each `&&` / `||` / `??` operator.\n * @param {ts.Node} rootNode - The AST root node to analyse — a whole `ts.SourceFile` for\n * file-level totals, or any function-like node to score it in isolation.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: ts.Node): number {\n let complexity = 1;\n\n function walkCyclomatic(node: ts.Node): void {\n switch (node.kind) {\n case ts.SyntaxKind.IfStatement:\n case ts.SyntaxKind.ConditionalExpression:\n case ts.SyntaxKind.ForStatement:\n case ts.SyntaxKind.ForInStatement:\n case ts.SyntaxKind.ForOfStatement:\n case ts.SyntaxKind.WhileStatement:\n case ts.SyntaxKind.DoStatement:\n case ts.SyntaxKind.CatchClause:\n case ts.SyntaxKind.CaseClause:\n complexity++;\n break;\n case ts.SyntaxKind.BinaryExpression: {\n const operatorKind = (node as ts.BinaryExpression).operatorToken.kind;\n if (\n operatorKind === ts.SyntaxKind.AmpersandAmpersandToken ||\n operatorKind === ts.SyntaxKind.BarBarToken ||\n operatorKind === ts.SyntaxKind.QuestionQuestionToken\n ) {\n complexity++;\n }\n break;\n }\n }\n ts.forEachChild(node, walkCyclomatic);\n }\n\n walkCyclomatic(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for an AST\n * node, tracking how hard the code is to read by adding a nesting penalty. Structural nodes\n * (`if`, loops, `switch`, `catch`) increment by `1 + current nesting depth` and increase the\n * depth for their children. Chained `else if` gets +1 (no nesting bonus). A bare `else` gets\n * +1. Logical operators and ternaries each add +1 without nesting. Nested functions (lambdas,\n * inner functions) add `1 + depth` and increase nesting.\n * @param {ts.Node} rootNode - The AST root node to analyse — a whole `ts.SourceFile` for\n * file-level totals, or any function-like node to score it in isolation (nesting depth\n * resets to 0 at `rootNode`).\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: ts.Node): number {\n let cognitiveComplexity = 0;\n\n function walkCognitive(node: ts.Node, depth: number, isElseIf: boolean): void {\n if (ts.isIfStatement(node)) {\n // else-if chains: flat +1; fresh if: +1 + nesting\n cognitiveComplexity += isElseIf ? 1 : 1 + depth;\n const bodyDepth = isElseIf ? depth : depth + 1;\n walkCognitive(node.expression, bodyDepth, false);\n walkCognitive(node.thenStatement, bodyDepth, false);\n if (node.elseStatement) {\n if (ts.isIfStatement(node.elseStatement)) {\n walkCognitive(node.elseStatement, depth, true);\n } else {\n cognitiveComplexity += 1; // bare else\n walkCognitive(node.elseStatement, depth + 1, false);\n }\n }\n return;\n }\n\n if (\n ts.isForStatement(node) ||\n ts.isForInStatement(node) ||\n ts.isForOfStatement(node) ||\n ts.isWhileStatement(node) ||\n ts.isDoStatement(node) ||\n ts.isSwitchStatement(node)\n ) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth + 1, false));\n return;\n }\n\n if (ts.isCatchClause(node)) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth, false));\n return;\n }\n\n if (ts.isConditionalExpression(node)) {\n cognitiveComplexity += 1;\n }\n\n if (ts.isBinaryExpression(node)) {\n const operatorKind = node.operatorToken.kind;\n if (\n operatorKind === ts.SyntaxKind.AmpersandAmpersandToken ||\n operatorKind === ts.SyntaxKind.BarBarToken ||\n operatorKind === ts.SyntaxKind.QuestionQuestionToken\n ) {\n cognitiveComplexity += 1;\n }\n }\n\n // Nested functions and lambdas increase nesting for their body\n const isNestedFunction =\n depth > 0 &&\n (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node));\n if (isNestedFunction) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth + 1, false));\n return;\n }\n\n ts.forEachChild(node, (child) => walkCognitive(child, depth, false));\n }\n\n walkCognitive(rootNode, 0, false);\n return cognitiveComplexity;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and a simplified SonarSource-style\n * cognitive complexity for a TypeScript/JavaScript AST node, by composing\n * `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {ts.Node} node - The AST root node to analyse — a whole `ts.SourceFile` for file-level\n * totals, or any function-like node to score it in isolation.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores, minimum 1 / 0 respectively.\n */\nexport function computeComplexity(node: ts.Node): {\n complexity: number;\n cognitiveComplexity: number;\n} {\n return {\n complexity: computeCyclomaticComplexity(node),\n cognitiveComplexity: computeCognitiveComplexity(node),\n };\n}\n","/** Collects structured tags from a TypeScript/JavaScript AST node using declaration names, @marker strings, comment annotations, and Vitest/Playwright option bags. */\nimport ts from \"typescript\";\nimport type { TagKind } from \"../../types/parse\";\nimport type { ParseContext } from \"../types\";\n\nconst TEST_CALL_NAMES = new Set([\"test\", \"describe\", \"it\"]);\n\n/**\n * @description Collects tags from a single AST node into `ctx.tags` using four strategies:\n * declaration names, string-literal `@` markers, comment `@tag` annotations, and\n * Vitest/Playwright option-bag arrays. Each strategy applies its own type guard so only\n * relevant nodes produce output.\n * @param node - The AST node currently being visited.\n * @param ctx - Mutable parse context accumulating tags for the current source file.\n */\nexport function handleTagging(node: ts.Node, ctx: ParseContext): void {\n collectDeclarationNameTags(node, ctx);\n collectStringLiteralAtTags(node, ctx);\n collectCommentAnnotationTags(node, ctx);\n collectVitestOptionBagTags(node, ctx);\n}\n\n/**\n * @description Adds the name of any top-level function or variable declaration to `ctx.tags`,\n * tagging it as `\"function\"` or `\"variable\"` based on its initializer.\n * Declarations nested inside callbacks or test blocks are skipped to avoid noise.\n * @param node - The AST node being visited.\n * @param ctx - Mutable parse context that receives the new tag.\n */\nfunction collectDeclarationNameTags(node: ts.Node, ctx: ParseContext): void {\n if (\n (ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node)) &&\n node.name &&\n ts.isIdentifier(node.name) &&\n isTopLevel(node)\n ) {\n let kind: TagKind;\n if (ts.isFunctionDeclaration(node)) {\n kind = \"function\";\n } else {\n const init = node.initializer;\n kind =\n init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))\n ? \"function\"\n : \"variable\";\n }\n ctx.tags.add({ name: node.name.text, kind });\n }\n}\n\n/**\n * @description Determines whether a function or variable declaration sits directly under the\n * source file root, distinguishing top-level exports from declarations nested in callbacks or blocks.\n * @param node - A function or variable declaration node to test.\n * @returns True if the node is a direct child of the `SourceFile`.\n */\nfunction isTopLevel(node: ts.FunctionDeclaration | ts.VariableDeclaration): boolean {\n if (ts.isFunctionDeclaration(node)) return ts.isSourceFile(node.parent);\n const stmt = node.parent?.parent; // VariableDeclarationList → VariableStatement\n return !!stmt && ts.isSourceFile(stmt.parent);\n}\n\n/**\n * @description Scans a string literal for `@word` patterns and records each matched word\n * as a `comment-marker` tag, enabling tag extraction from test-title strings like `'login @smoke'`.\n * @param node - The AST node to inspect; only string literals produce output.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectStringLiteralAtTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isStringLiteral(node)) return;\n const matches = node.text.match(/@[\\w-]+/g);\n if (matches) {\n for (const tag of matches) ctx.tags.add({ name: tag.substring(1), kind: \"comment-marker\" });\n }\n}\n\n/**\n * @description Scans the full source text for `@tag <name>` annotations and records each `<name>`\n * as a `comment-marker` tag. Only runs when `node` is the `SourceFile` so the text is scanned exactly once per file.\n * @param node - The current AST node; processing is skipped unless it is a `SourceFile`.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectCommentAnnotationTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isSourceFile(node)) return;\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n const fullText = node.getFullText();\n let match = tagRegex.exec(fullText);\n while (match !== null) {\n if (match[1]) ctx.tags.add({ name: match[1], kind: \"comment-marker\" });\n match = tagRegex.exec(fullText);\n }\n}\n\n/**\n * @description Inspects call expressions that match test-framework functions and extracts tags\n * from any object-literal argument. Handles both direct calls (`test(...)`) and chained forms\n * like `it.each(...)` or `describe.skip(...)`.\n * @param node - The AST node to inspect; only call expressions are processed.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectVitestOptionBagTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isCallExpression(node)) return;\n\n if (!isTestCallExpression(node.expression)) return;\n\n for (const arg of node.arguments) {\n if (!ts.isObjectLiteralExpression(arg)) continue;\n collectTagsFromObjectLiteral(arg, ctx);\n }\n}\n\n/**\n * @description Returns true if the callee expression resolves to a test-framework function\n * (`test`, `describe`, or `it`), recognising both bare identifiers and property-access\n * forms such as `it.skip` or `describe.concurrent`.\n * @param callee - The callee expression of a call node to classify.\n * @returns True when the expression refers to a known test-framework entry point.\n */\nfunction isTestCallExpression(callee: ts.Expression): boolean {\n if (ts.isIdentifier(callee)) return TEST_CALL_NAMES.has(callee.text);\n if (ts.isPropertyAccessExpression(callee)) {\n // `something.test(...)` / `something.describe(...)`\n if (TEST_CALL_NAMES.has(callee.name.text)) return true;\n // `it.skip(...)` / `test.concurrent(...)` — base is the test function\n if (ts.isIdentifier(callee.expression) && TEST_CALL_NAMES.has(callee.expression.text))\n return true;\n }\n return false;\n}\n\n/**\n * @description Reads the `tags` (Vitest array) or `tag` (Playwright string or array) property\n * from an object literal and records each value as a `comment-marker` tag, stripping any\n * leading `@` so both frameworks produce the same normalised tag name.\n * @param obj - The object literal expression from a test call's option argument.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectTagsFromObjectLiteral(obj: ts.ObjectLiteralExpression, ctx: ParseContext): void {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text !== \"tags\" && prop.name.text !== \"tag\") continue;\n\n const { initializer } = prop;\n const values: ts.StringLiteral[] = ts.isArrayLiteralExpression(initializer)\n ? initializer.elements.filter(ts.isStringLiteral)\n : prop.name.text === \"tag\" && ts.isStringLiteral(initializer)\n ? [initializer]\n : [];\n\n for (const el of values) {\n ctx.tags.add({ name: el.text.replace(/^@/, \"\"), kind: \"comment-marker\" });\n }\n }\n}\n","/** Classifies CSS/Less files as barrels (import-only) or UI files based on PostCSS AST analysis. */\nimport type postcss from \"postcss\";\nimport type { ImportEdge } from \"../../types/node\";\nimport type { NodeCategory } from \"../../types/parse\";\n\n// TODO(SOLID-I): only `imports.length` is read; parameter could be narrowed to `{ length: number }`\n/**\n * @description Classifies a CSS or Less file as a barrel (imports only) or a UI file (contains CSS rules).\n * @param {postcss.Root} root - The PostCSS AST of the parsed file; walked to detect any `rule` nodes\n * @param {ImportEdge[]} imports - The edges already extracted from the file; only the count is used to short-circuit empty files\n * @returns {NodeCategory} `\"barrel\"` when the file has imports but no CSS rules, `\"ui\"` otherwise\n */\nexport function detectCssBarrel(root: postcss.Root, imports: ImportEdge[]): NodeCategory {\n if (imports.length === 0) return \"ui\";\n let hasRule = false;\n root.walk((node) => {\n if (node.type === \"rule\") {\n hasRule = true;\n return false;\n }\n });\n return hasRule ? \"ui\" : \"barrel\";\n}\n","/** Parses CSS and Less files using PostCSS to extract @import edges. */\nimport postcss from \"postcss\";\nimport type { ImportEdge } from \"../../types/node\";\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst lessParser = require(\"postcss-less\") as {\n parse: postcss.Parser<postcss.Root>;\n stringify: postcss.Stringifier;\n};\n\nconst SIDE_EFFECT_KEYWORDS = new Set([\"reference\", \"inline\"]);\n\n/**\n * @description Returns true when a CSS import specifier points to an external resource rather than a local file.\n * @param {string} specifier - The raw import path as written in the source (e.g. `~bootstrap`, `https://…`)\n * @returns {boolean} `true` for tilde-prefixed node_modules, absolute URLs, protocol-relative URLs, and data URIs\n */\nfunction isExternalCss(specifier: string): boolean {\n return (\n specifier.startsWith(\"~\") ||\n specifier.startsWith(\"http://\") ||\n specifier.startsWith(\"https://\") ||\n specifier.startsWith(\"//\") ||\n specifier.startsWith(\"data:\")\n );\n}\n\n/**\n * @description Returns true when a `url()` value refers to a file on disk rather than an external or fragment URL.\n * @param {string} specifier - The raw value extracted from a `url()` expression, before any trimming\n * @returns {boolean} `true` for relative or absolute local paths; `false` for HTTP URLs, protocol-relative URLs, data URIs, and hash fragments\n */\nfunction isLocalUrl(specifier: string): boolean {\n const trimmed = specifier.trim();\n return (\n trimmed.length > 0 &&\n !trimmed.startsWith(\"http://\") &&\n !trimmed.startsWith(\"https://\") &&\n !trimmed.startsWith(\"//\") &&\n !trimmed.startsWith(\"data:\") &&\n !trimmed.startsWith(\"#\")\n );\n}\n\n/**\n * @description Parses the params string of a PostCSS `@import` at-rule into a single import edge.\n * Handles three syntaxes: Less modifier form `(keyword) \"path\"`, `url(\"path\")`, and bare `\"path\"`.\n * @param {string} params - The raw text after `@import`, exactly as PostCSS exposes it (no leading `@import`)\n * @param {string} filePath - Absolute path of the file being parsed, used as the `fromPath` of the edge\n * @returns {ImportEdge | null} An `ImportEdge` when the params contain a recognisable import path, or `null` for empty or malformed params\n */\nfunction extractAtImportEdge(params: string, filePath: string): ImportEdge | null {\n // Less modifier: (keyword) \"path\" or (keyword) 'path'\n const lessMatch = params.match(/^\\(([^)]+)\\)\\s+['\"]([^'\"]+)['\"]/);\n if (lessMatch) {\n const keyword = lessMatch[1]?.trim() ?? \"\";\n const specifier = lessMatch[2] ?? \"\";\n if (!specifier) return null;\n const type = SIDE_EFFECT_KEYWORDS.has(keyword) ? \"side-effect\" : \"static\";\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type,\n ...(isExternalCss(specifier) ? { isExternal: true } : {}),\n };\n }\n // url() form: url(\"path\") or url('path') or url(path)\n const urlMatch = params.match(/^url\\(['\"]?([^'\")]+)['\"]?\\)/);\n const specifier = urlMatch\n ? (urlMatch[1]?.trim() ?? \"\")\n : (params.match(/^['\"]([^'\"]+)['\"]/)?.[1] ?? \"\");\n if (!specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n ...(isExternalCss(specifier) ? { isExternal: true } : {}),\n };\n}\n\n/**\n * @description Extracts all local `url()` references from a single CSS declaration value as import edges.\n * @param {string} value - The raw CSS property value string (e.g. `url(\"./bg.png\") center`)\n * @param {string} filePath - Absolute path of the file being parsed, used as `fromPath` on each edge\n * @returns {ImportEdge[]} One edge per local `url()` found; external URLs and data URIs are skipped\n */\nfunction extractUrlDeclarationEdges(value: string, filePath: string): ImportEdge[] {\n const edges: ImportEdge[] = [];\n const urlPattern = /url\\(['\"]?([^'\")]+)['\"]?\\)/g;\n let match = urlPattern.exec(value);\n while (match !== null) {\n const specifier = match[1]?.trim() ?? \"\";\n if (isLocalUrl(specifier)) {\n edges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n });\n }\n match = urlPattern.exec(value);\n }\n return edges;\n}\n\n/**\n * @description Walks a parsed PostCSS tree and collects every import edge — both `@import` at-rules and `url()` references in declarations.\n * @param {postcss.Root} root - The PostCSS root node produced by parsing a CSS or Less file\n * @param {string} filePath - Absolute path of the source file; forwarded to edge constructors as `fromPath`\n * @returns {ImportEdge[]} All import edges found in the tree, in document order\n */\nfunction collectEdgesFromRoot(root: postcss.Root, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n root.walk((node) => {\n if (node.type === \"atrule\" && node.name === \"import\") {\n const edge = extractAtImportEdge(node.params, filePath);\n if (edge) imports.push(edge);\n }\n if (node.type === \"decl\") {\n imports.push(...extractUrlDeclarationEdges(node.value, filePath));\n }\n });\n return imports;\n}\n\n/**\n * @description Removes `//` line comments from CSS source so PostCSS can parse files that use non-standard comment syntax.\n * @param {string} content - Raw CSS file contents, potentially containing `//` comments\n * @returns {string} The content with `//`-to-end-of-line sequences removed, leaving `://` (URLs) intact\n */\nfunction stripLineComments(content: string): string {\n // `//` is not valid CSS but is widely used; strip before passing to PostCSS.\n // Negative lookbehind on `:` avoids stripping `//` inside `https://` or `http://` URLs.\n return content.replace(/(?<!:)\\/\\/.*/g, \"\");\n}\n\n/**\n * @description Extracts `@import` edges from raw CSS/Less source using a regex when the PostCSS parser fails.\n * Only captures `@import` at-rules; `url()` references in declarations are not extracted here.\n * @param {string} content - Raw file contents that could not be parsed by PostCSS\n * @param {string} filePath - Absolute path of the file being parsed, used as `fromPath` on each edge\n * @returns {ImportEdge[]} All `@import` edges found by pattern matching, with no barrel/side-effect detection for url() forms\n */\nfunction regexFallbackImports(content: string, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n const atImportPattern = /@import\\s+(?:\\(([^)]+)\\)\\s+)?['\"]([^'\"]+)['\"]/g;\n let match = atImportPattern.exec(content);\n while (match !== null) {\n const keyword = match[1]?.trim() ?? \"\";\n const specifier = match[2] ?? \"\";\n if (specifier) {\n const type = SIDE_EFFECT_KEYWORDS.has(keyword) ? \"side-effect\" : \"static\";\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type,\n });\n }\n match = atImportPattern.exec(content);\n }\n return imports;\n}\n\n/**\n * @description Parses a CSS file and returns its import edges alongside the PostCSS AST.\n * Strips non-standard `//` line comments before parsing so that common CSS-in-JS and preprocessor conventions do not cause a parse error.\n * @param {string} content - Raw CSS file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root, which callers use for barrel detection\n */\nexport function parseCssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n // Strip // line comments — not valid CSS but common; PostCSS throws on them\n const root = postcss.parse(stripLineComments(content));\n return { imports: collectEdgesFromRoot(root, filePath), root };\n}\n\n/**\n * @description Parses a Less file and returns its import edges alongside the PostCSS AST.\n * Falls back to regex-only extraction when `postcss-less` throws, so mixed or malformed Less files still yield at least the `@import` edges.\n * @param {string} content - Raw Less file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root (may be empty on parse failure)\n */\nexport function parseLessContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n try {\n const root = lessParser.parse(content);\n return { imports: collectEdgesFromRoot(root, filePath), root };\n } catch {\n // Fallback when content mixes non-Less syntax (e.g., bare `import` without @).\n // Use regex to extract @import edges only; barrel detection gets an empty root.\n return { imports: regexFallbackImports(content, filePath), root: postcss.parse(\"\") };\n }\n}\n","/** Parses SCSS/Sass files using postcss-scss to extract @use, @forward, and @import edges. */\nimport type postcss from \"postcss\";\nimport { parse as scssParse } from \"postcss-scss\";\nimport type { ImportEdge } from \"../../types/node\";\n\n/**\n * @description Returns true when a SCSS/Sass import specifier resolves outside the local file tree.\n * @param {string} specifier - The raw import path as written in source (e.g. `sass:color`, `~bootstrap`, `./tokens`)\n * @returns {boolean} `true` for built-in Sass namespaces, tilde node_modules shortcuts, HTTP/protocol-relative URLs, and bare package names\n */\nfunction isScssExternal(specifier: string): boolean {\n // Built-in Sass namespaces (sass:color, sass:math, etc.)\n if (specifier.startsWith(\"sass:\")) return true;\n // Webpack/Less tilde convention for node_modules\n if (specifier.startsWith(\"~\")) return true;\n // HTTP/protocol-relative URLs\n if (\n specifier.startsWith(\"http://\") ||\n specifier.startsWith(\"https://\") ||\n specifier.startsWith(\"//\")\n )\n return true;\n // Bare package name: no leading `.`, `/`, or `_` (Sass partial convention)\n if (!specifier.startsWith(\".\") && !specifier.startsWith(\"/\") && !specifier.startsWith(\"_\"))\n return true;\n return false;\n}\n\n/**\n * @description Extracts the import path and optional namespace alias from a SCSS `@use` or `@forward` params string.\n * @param {string} params - The raw text after the at-rule keyword (e.g. `\"./tokens\" as t`)\n * @returns {{ specifier: string; alias?: string }} The resolved specifier and, when an `as` clause is present, the alias name\n */\nfunction parseScssParams(params: string): { specifier: string; alias?: string } {\n const specMatch = params.match(/^['\"]([^'\"]+)['\"]/);\n if (!specMatch?.[1]) return { specifier: \"\" };\n const specifier = specMatch[1];\n const asMatch = params.match(/\\bas\\s+(\\S+)/);\n const alias = asMatch?.[1];\n return alias !== undefined ? { specifier, alias } : { specifier };\n}\n\n/**\n * @description Parses a SCSS file and returns its import edges alongside the PostCSS AST.\n * Recognises `@import`, `@use`, and `@forward` at-rules; marks `@forward` edges as `re-export` and attaches namespace aliases when an `as` clause is present.\n * @param {string} content - Raw SCSS file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root, which callers use for barrel detection\n */\nexport function parseScssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n const root = scssParse(content) as postcss.Root;\n const imports: ImportEdge[] = [];\n\n root.walk((node) => {\n if (node.type !== \"atrule\") return;\n const { name, params } = node;\n if (name !== \"import\" && name !== \"use\" && name !== \"forward\") return;\n\n const { specifier, alias } = parseScssParams(params);\n if (!specifier) return;\n\n const edge: ImportEdge = {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: name === \"forward\" ? \"re-export\" : \"static\",\n ...(isScssExternal(specifier) ? { isExternal: true } : {}),\n };\n if (alias) edge.symbols = [alias];\n imports.push(edge);\n });\n\n return { imports, root };\n}\n","/** Parses Stylus files to extract @require and bare import/require dependency edges. */\nimport type { ImportEdge } from \"../../types/node\";\n\n/**\n * @description Extracts all import edges from a Stylus file, covering both `@require` and bare `import`/`require` forms.\n * @param {string} content - Raw Stylus file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {ImportEdge[]} All import edges found, with `@require` entries typed as `\"require\"` and bare forms as `\"static\"`\n */\nexport function parseStylusImports(content: string, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n\n const atRequirePattern = /@require\\s+['\"]([^'\"]+)['\"]/g;\n let match = atRequirePattern.exec(content);\n while (match !== null) {\n const specifier = match[1];\n if (specifier) {\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"require\",\n });\n }\n match = atRequirePattern.exec(content);\n }\n\n // Negative lookbehind on @ avoids re-matching @require entries above\n const bareImportPattern = /(?<!@)(?:import|require)\\s*\\(?\\s*['\"]([^'\"]+)['\"]/g;\n match = bareImportPattern.exec(content);\n while (match !== null) {\n const specifier = match[1];\n if (specifier) {\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n });\n }\n match = bareImportPattern.exec(content);\n }\n\n return imports;\n}\n\n// TODO(SOLID-I): only `imports.length` is read; parameter could be narrowed to `{ length: number }`\n/**\n * @description Classifies a Stylus file as a barrel (re-exports only) or a UI file (contains rules or styles).\n * Attempts AST analysis via the optional `stylus` library; falls back to regex stripping when unavailable.\n * @param {string} content - Raw Stylus file contents, used for both AST parsing and the regex fallback\n * @param {ImportEdge[]} imports - The edges already extracted from the file; only the count is used to short-circuit empty files\n * @returns {\"ui\" | \"barrel\"} `\"barrel\"` when the file contains only imports, `\"ui\"` when it also defines rules or styles\n */\nexport function detectStylusCategory(content: string, imports: ImportEdge[]): \"ui\" | \"barrel\" {\n if (imports.length === 0) return \"ui\";\n\n // Try Stylus AST for files using @require/@import (common form).\n // The Stylus Parser AST correctly identifies Import vs rule Group nodes.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const stylusLib = require(\"stylus\") as {\n Parser: new (src: string) => { parse(): { nodes: Array<{ constructor: { name: string } }> } };\n };\n const ast = new stylusLib.Parser(content).parse();\n const hasNonImport = ast.nodes.some((astNode) => astNode.constructor.name !== \"Import\");\n return hasNonImport ? \"ui\" : \"barrel\";\n } catch {\n // Fallback: strip all import/require lines and check if any content remains.\n // Handles bare `import 'path'`, `require('path')`, and `@require 'path'` forms.\n const withoutImports = content.replace(/^\\s*@?(?:require|import)\\b.*/gm, \"\").trim();\n return withoutImports.length > 0 ? \"ui\" : \"barrel\";\n }\n}\n","/** Dispatches style file parsing to the appropriate dialect handler (CSS, Less, SCSS, Sass, Stylus). */\nimport { getFileType } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { detectCssBarrel } from \"./barrel\";\nimport { parseCssContent, parseLessContent } from \"./css\";\nimport { parseScssContent } from \"./scss\";\nimport { detectStylusCategory, parseStylusImports } from \"./stylus\";\n\n// TODO(SOLID-O): adding a new style dialect (e.g. Sass indented) requires editing this function; consider a parser registry keyed by file type\n/**\n * @description Parses a style file of any supported dialect and returns a normalised `ParseResult`.\n * Delegates to the dialect-specific parser based on the file extension, then wraps the result in the standard shape with empty `exports` and `tags`.\n * @param {string} filePath - Absolute path to the style file; determines which parser is selected\n * @param {string} content - Raw file contents to parse\n * @returns {ParseResult} Import edges, empty exports/tags, and a category classification for the file\n */\nexport function parseStyleFile(filePath: string, content: string): ParseResult {\n const fileType = getFileType(filePath);\n\n if (fileType === \"stylus\") {\n const imports = parseStylusImports(content, filePath);\n return {\n imports,\n exports: [],\n tags: [],\n category: detectStylusCategory(content, imports),\n };\n }\n\n if (fileType === \"scss\") {\n const { imports, root } = parseScssContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n }\n\n if (fileType === \"less\") {\n const { imports, root } = parseLessContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n }\n\n // css (and any unknown style type)\n const { imports, root } = parseCssContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n}\n","/** Aggregates all language parsers and exposes parseFile and parseImports as the unified entry points. */\nimport { getFileType } from \"./parser/file-type\";\nimport { parseCoffeeScript } from \"./parser/lang/coffee\";\nimport { parseGherkin } from \"./parser/lang/gherkin\";\nimport { parseGo } from \"./parser/lang/go\";\nimport { parseLiveScript } from \"./parser/lang/ls\";\nimport { parseLua } from \"./parser/lang/lua\";\nimport { parsePython } from \"./parser/lang/python\";\nimport { parseCodeFile } from \"./parser/lang/typescript\";\nimport type { ParserFunction } from \"./parser/registry\";\nimport { getParserForType, registerParser } from \"./parser/registry\";\nimport { parseStyleFile } from \"./parser/style\";\nimport type { ParseResult } from \"./parser/types\";\nimport type { ImportEdge } from \"./types/node\";\nimport type { FileType } from \"./types/parse\";\n\nfor (const [type, parser] of [\n [\"javascript\", (path, content) => parseCodeFile(path, content, \"javascript\")],\n [\"typescript\", (path, content) => parseCodeFile(path, content, \"typescript\")],\n [\"css\", parseStyleFile],\n [\"scss\", parseStyleFile],\n [\"less\", parseStyleFile],\n [\"stylus\", parseStyleFile],\n [\"coffeescript\", parseCoffeeScript],\n [\"livescript\", parseLiveScript],\n [\"lua\", parseLua],\n [\"python\", parsePython],\n [\"go\", parseGo],\n [\"gherkin\", parseGherkin],\n] satisfies [FileType, ParserFunction][]) {\n registerParser(type, parser);\n}\n\nexport {\n getBarrelThreshold,\n getTestLibraries,\n getTestPatterns,\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n setBarrelThreshold,\n} from \"./parser/classify.js\";\nexport { getFileType } from \"./parser/file-type.js\";\nexport { registerParser } from \"./parser/registry.js\";\n\n/**\n * @description Main entry point for parsing a file. Dispatches to a registered\n * language-specific parser based on the file's extension, falling back to an\n * empty result for unknown types.\n * @param {string} filePath - Absolute or relative path to the file; determines which parser is used.\n * @param {string} content - Raw source content of the file.\n * @returns {Promise<ParseResult>} Parsed imports, exports, tags, and category for the file.\n */\nexport async function parseFile(filePath: string, content: string): Promise<ParseResult> {\n const fileType = getFileType(filePath);\n const parser = getParserForType(fileType);\n\n if (parser) {\n return parser(filePath, content);\n }\n\n return { imports: [], exports: [], tags: [], category: \"other\" };\n}\n\n/**\n * @description Parses a file and returns only its import edges, discarding exports, tags, and category.\n * @param {string} filePath - Path to the file being parsed; determines the parser to use.\n * @param {string} content - Raw source content of the file.\n * @returns {Promise<ImportEdge[]>} All import edges extracted from the file.\n */\nexport async function parseImports(filePath: string, content: string): Promise<ImportEdge[]> {\n const result = await parseFile(filePath, content);\n return result.imports;\n}\n","/** Post-build enrichment passes that annotate graph nodes with coverage, library tags, test links, and export-usage ratios. */\nimport path from \"node:path\";\nimport type { FileNode, ImportEdge, StructuredTag } from \"../types/node\";\n\n/**\n * @description Annotates each node with its line-coverage percentage from a pre-loaded\n * coverage map. Nodes not present in the map are left untouched (`coveragePct` remains\n * undefined). Only called when the map is non-empty.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n * @param coverageMap - Map of project-relative path → line-coverage percentage (0–100).\n */\nexport function enrichCoverage(\n nodes: Map<string, FileNode>,\n coverageMap: Map<string, number>,\n): void {\n for (const node of nodes.values()) {\n const pct = coverageMap.get(node.path);\n if (pct !== undefined) node.coveragePct = pct;\n }\n}\n\n/**\n * @description Scans a file's import edges and appends a structured `import`-kind tag for every\n * third-party library found. Scoped packages (`@scope/pkg/deep`) are normalised to their\n * two-segment name before deduplication.\n * @param imports - The resolved import edges for the file being enriched.\n * @param tags - The tag array for that same file; modified in place.\n */\nexport function enrichLibraryTags(imports: ImportEdge[], tags: StructuredTag[]): void {\n for (const imp of imports) {\n if (!imp.rawSpecifier.startsWith(\".\") && !path.isAbsolute(imp.rawSpecifier)) {\n const libName = imp.rawSpecifier.startsWith(\"@\")\n ? imp.rawSpecifier.split(\"/\").slice(0, 2).join(\"/\")\n : imp.rawSpecifier.split(\"/\")[0];\n if (libName && !tags.some((existingTag) => existingTag.name === libName)) {\n tags.push({ name: libName, kind: \"library\" });\n }\n }\n }\n}\n\n/**\n * @description Walks every test node in the graph and records it as a tester of each\n * `logic` or `barrel` node it imports. Populates `FileNode.testedBy` so that an AI\n * (or human) can ask \"what tests cover this file?\" without re-running dynamic analysis.\n * Only internal, resolved imports are considered; external imports are ignored.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichTestedBy(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.category !== \"test\") continue;\n for (const imp of node.imports) {\n if (imp.isExternal || !imp.toPath) continue;\n const target = nodes.get(imp.toPath);\n if (!target) continue;\n if (target.category !== \"logic\" && target.category !== \"barrel\") continue;\n target.testedBy ??= [];\n if (!target.testedBy.includes(node.path)) target.testedBy.push(node.path);\n }\n }\n}\n\nfunction round4(value: number): number {\n return Math.round(value * 10000) / 10000;\n}\n/**\n * @description Computes a `exportUsageRatio` for each internal import edge and aggregates\n * `avgExportUsage` and `maxExportUsage` per node. The ratio is the fraction of the target\n * file's exports consumed by this import (`importedSymbols / target.exports.length`).\n * Namespace imports (`[\"*\"]`) and unresolved re-exports are treated as full usage (1.0).\n * Side-effect imports and edges where the target has zero exports are skipped.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichExportUsage(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n const ratios: number[] = [];\n for (const imp of node.imports) {\n if (imp.isExternal || !imp.toPath) continue;\n const target = nodes.get(imp.toPath);\n if (!target || target.exports.length === 0) continue;\n\n let ratio: number;\n if (imp.symbols === undefined) {\n if (imp.type === \"side-effect\") continue;\n ratio = 1.0;\n } else if (imp.symbols.includes(\"*\")) {\n ratio = 1.0;\n } else {\n ratio = imp.symbols.length / target.exports.length;\n }\n\n imp.exportUsageRatio = round4(Math.min(1, ratio));\n ratios.push(imp.exportUsageRatio);\n }\n\n if (ratios.length > 0) {\n node.avgExportUsage = round4(ratios.reduce((sum, ratio) => sum + ratio, 0) / ratios.length);\n node.maxExportUsage = Math.max(...ratios);\n }\n }\n}\n\n/**\n * @description Appends `{ name, kind }` to `tags` unless an entry with the same `name` and\n * `kind` already exists. Centralises the dedup check shared by every tag-adding step in\n * `enrichTestNodeTags`.\n * @param {StructuredTag[]} tags - The tag array to append to; mutated in place.\n * @param {string} name - The tag name to add.\n * @param {StructuredTag[\"kind\"]} kind - The tag kind to add.\n */\nfunction addUniqueTag(tags: StructuredTag[], name: string, kind: StructuredTag[\"kind\"]): void {\n if (!name) return;\n if (tags.some((existingTag) => existingTag.name === name && existingTag.kind === kind)) return;\n tags.push({ name, kind });\n}\n\n/**\n * @description Adds a filename-derived `import` tag to `testNode` for the file it imports\n * (e.g. a test importing `graph/builder.ts` receives the tag `builder`). Test-suffix\n * extensions (`.test`/`.spec`) are stripped from the derived name.\n * @param {FileNode} testNode - The test node receiving the tag; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge to derive the tag from.\n */\nfunction addFilenameTag(testNode: FileNode, importEdge: ImportEdge): void {\n const toPath = importEdge.toPath as string;\n const filenameTag = path.basename(toPath, path.extname(toPath)).replace(/\\.(test|spec)$/, \"\");\n addUniqueTag(testNode.tags, filenameTag, \"import\");\n}\n\n/**\n * @description Adds an `import` tag for each named symbol imported by `importEdge`.\n * Namespace imports (`[\"*\"]`) are skipped since there is no single symbol name to tag.\n * @param {FileNode} testNode - The test node receiving the tags; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge whose `symbols` are tagged.\n */\nfunction addSymbolTags(testNode: FileNode, importEdge: ImportEdge): void {\n if (!importEdge.symbols || importEdge.symbols.includes(\"*\")) return;\n for (const symbolName of importEdge.symbols) {\n addUniqueTag(testNode.tags, symbolName, \"import\");\n }\n}\n\n/**\n * @description Propagates `comment-marker` tags (e.g. `@tag auth` in the source file) from\n * the imported node to `testNode`, so tests inherit the semantic markers of what they test.\n * Skips imports that resolve to another test node.\n * @param {FileNode} testNode - The test node receiving the tags; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge whose target is inspected.\n * @param {Map<string, FileNode>} nodes - The full node map, used to look up the import target.\n */\nfunction propagateCommentMarkers(\n testNode: FileNode,\n importEdge: ImportEdge,\n nodes: Map<string, FileNode>,\n): void {\n const sourceNode = nodes.get(importEdge.toPath as string);\n if (!sourceNode || sourceNode.category === \"test\") return;\n for (const sourceTag of sourceNode.tags) {\n if (sourceTag.kind !== \"comment-marker\") continue;\n addUniqueTag(testNode.tags, sourceTag.name, \"comment-marker\");\n }\n}\n\n/**\n * @description Adds tags derived from each local import to the importing test node.\n * Two tag kinds are applied: a filename-derived `import` tag (e.g. a test importing\n * `graph/builder.ts` receives the tag `builder`), and any `comment-marker` tags\n * propagated from the source node (e.g. `@tag auth` in `auth/service.ts` propagates\n * to tests that import it). `function` and `variable` kind tags are intentionally skipped\n * as they are too granular for test filtering. Existing duplicate tags are skipped.\n * @param {Map<string, FileNode>} nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichTestNodeTags(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.category !== \"test\") continue;\n for (const importEdge of node.imports) {\n if (!importEdge.toPath || importEdge.isExternal) continue;\n\n addFilenameTag(node, importEdge);\n addSymbolTags(node, importEdge);\n propagateCommentMarkers(node, importEdge, nodes);\n }\n }\n}\n","/** DefaultResolver turns raw import specifiers into absolute file paths, handling relative paths, tsconfig aliases, workspace packages, and node_modules. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n GoLangResolver,\n type LangResolver,\n LuaLangResolver,\n PythonLangResolver,\n type ResolvedImport,\n} from \"./lang-resolvers/index\";\n\nexport type { ResolvedImport };\n\n/**\n * @description Contract for resolving an import specifier to one or more absolute file paths\n * given the file that contains the import.\n */\nexport interface PathResolver {\n /**\n * @description Resolves an import specifier to an absolute path and whether it is\n * outside the project root. Returns the first result when multiple files are possible\n * (e.g. a Go package directory). Use `resolveAll` to get every file.\n * @param currentFile - Absolute path of the file containing the import statement.\n * @param specifier - The raw import specifier string (e.g. `\"./utils\"` or `\"lodash\"`).\n * @returns Resolved path and external flag, or `null` if resolution fails.\n */\n resolve(currentFile: string, specifier: string): ResolvedImport | null;\n\n /**\n * @description Resolves an import specifier to all matching local files. For most languages\n * this is identical to `resolve` (one file). For Go packages it returns every non-test\n * `.go` file in the target directory. Returns an empty array if resolution fails.\n * @param currentFile - Absolute path of the file containing the import statement.\n * @param specifier - The raw import specifier string.\n * @returns Array of resolved imports (may be empty).\n */\n resolveAll(currentFile: string, specifier: string): ResolvedImport[];\n}\n\n/** @description Configuration options for `DefaultResolver`, used to support monorepo and alias-aware resolution. */\nexport interface ResolverOptions {\n /**\n * Maps workspace package names to their absolute root directories.\n * When set, matching specifiers are resolved as internal workspace imports\n * rather than external npm packages.\n */\n workspaceMap?: Map<string, string>;\n /**\n * Ordered list of directories to search for `tsconfig.json` when resolving\n * path aliases. Defaults to `[rootDir]`. For monorepo builds, pass\n * `[packageRoot, monorepoRoot]` so per-package aliases take precedence.\n */\n tsconfigSearchPaths?: string[];\n /**\n * Language-specific resolvers that handle bare (non-relative) specifiers before\n * falling through to the external-module default. Defaults to Python, Lua, and Go resolvers.\n */\n langResolvers?: LangResolver[];\n}\n\n/**\n * @description Default import resolver that handles relative paths, absolute paths,\n * tsconfig path aliases, workspace packages, Lua dot-separated modules, and external node_modules.\n */\nexport class DefaultResolver implements PathResolver {\n private readonly workspaceMap: Map<string, string>;\n private readonly tsconfigSearchPaths: string[];\n private readonly langResolvers: LangResolver[];\n\n /**\n * @param rootDir - Absolute path to the project root, used as the boundary for\n * deciding whether a resolved path is internal or external.\n * @param options - Optional workspace map, tsconfig search paths, and lang resolvers.\n */\n constructor(\n private rootDir: string,\n options: ResolverOptions = {},\n ) {\n this.workspaceMap = options.workspaceMap ?? new Map();\n this.tsconfigSearchPaths = options.tsconfigSearchPaths ?? [rootDir];\n this.langResolvers = options.langResolvers ?? [\n new PythonLangResolver(),\n new LuaLangResolver(),\n new GoLangResolver(),\n ];\n }\n\n /**\n * @description Resolves a specifier to all matching files. For most languages this returns\n * a single-element array (or empty on failure). For Go packages it returns one entry per\n * non-test `.go` file in the target directory.\n * @param currentFile - Absolute path of the file containing the import.\n * @param specifier - The raw import specifier to resolve.\n * @returns Array of resolved imports; empty if resolution fails.\n */\n public resolveAll(currentFile: string, specifier: string): ResolvedImport[] {\n // 1. Path aliases → always single result\n const aliased = this.resolvePathAlias(specifier);\n if (aliased) return [aliased];\n\n // 2. Relative / absolute local paths → always single result\n if (specifier.startsWith(\".\") || specifier.startsWith(\"/\")) {\n const resolved = this.resolveLocalPath(currentFile, specifier);\n return resolved ? [resolved] : [];\n }\n\n // 3. Language-specific — may return multiple files (e.g. Go packages)\n const resolveLocal = (cf: string, spec: string) => this.resolveLocalPath(cf, spec);\n for (const lr of this.langResolvers) {\n if (lr.extensions.some((ext) => currentFile.endsWith(ext))) {\n const locals = lr.resolve(currentFile, specifier, this.rootDir, resolveLocal);\n if (locals) return locals;\n }\n }\n\n // 4. Workspace package\n const workspace = this.resolveWorkspaceImport(specifier);\n if (workspace) return [workspace];\n\n // 5. External\n return [{ path: specifier, isExternal: true }];\n }\n\n /**\n * @description Resolves a specifier by trying path aliases first, then relative/absolute\n * local paths, then Lua dot-notation, and finally treating the specifier as an external module.\n * @param currentFile - Absolute path of the file containing the import.\n * @param specifier - The raw import specifier to resolve.\n * @returns Resolved path and external flag, or `null` if no local file can be found.\n */\n public resolve(currentFile: string, specifier: string): ResolvedImport | null {\n // 1. Try Path Aliases (tsconfig.json paths)\n const aliased = this.resolvePathAlias(specifier);\n if (aliased) return aliased;\n\n // 2. Handle Relative or Absolute Local Paths\n if (specifier.startsWith(\".\") || specifier.startsWith(\"/\")) {\n return this.resolveLocalPath(currentFile, specifier);\n }\n\n // 3. Language-specific resolution (Python, Lua, Go, …)\n const resolveLocal = (cf: string, spec: string) => this.resolveLocalPath(cf, spec);\n for (const lr of this.langResolvers) {\n if (lr.extensions.some((ext) => currentFile.endsWith(ext))) {\n const locals = lr.resolve(currentFile, specifier, this.rootDir, resolveLocal);\n if (locals) return locals[0] ?? null;\n }\n }\n\n // 4. Workspace package resolution — check before falling through to external\n const workspace = this.resolveWorkspaceImport(specifier);\n if (workspace) return workspace;\n\n // 5. Non-relative, non-absolute import (likely a node_module or built-in)\n return { path: specifier, isExternal: true };\n }\n\n /**\n * @description Resolves a relative or absolute specifier to a concrete file path by\n * trying multiple extensions and index-file fallbacks, including ESM `.js`→`.ts` rewriting.\n * @param currentFile - Absolute path of the importing file, used to compute the base directory.\n * @param specifier - A relative (`./foo`) or absolute (`/foo`) import specifier.\n * @returns Resolved path and external flag, or `null` if no matching file is found within the project.\n */\n private resolveLocalPath(currentFile: string, specifier: string): ResolvedImport | null {\n const dir = path.dirname(currentFile);\n const fullPath = specifier.startsWith(\"/\") ? specifier : path.resolve(dir, specifier);\n const isExternal = !fullPath.startsWith(this.rootDir);\n\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".css\",\n \".scss\",\n \".sass\",\n \".less\",\n \".styl\",\n \".coffee\",\n \".ls\",\n \".lua\",\n \".py\",\n \".feature\",\n ];\n\n // ESM Support: If specifier ends with .js/.mjs/.cjs, try stripping it to allow .ts resolution\n const esmMatch = fullPath.match(/\\.(js|mjs|cjs)$/);\n if (esmMatch) {\n const strippedPath = fullPath.slice(0, -esmMatch[0].length);\n for (const ext of [\".ts\", \".tsx\"]) {\n const resolved = this.tryExtensions(strippedPath, ext, isExternal);\n if (resolved) return resolved;\n }\n }\n\n for (const ext of extensions) {\n const resolved = this.tryExtensions(fullPath, ext, isExternal);\n if (resolved) return resolved;\n }\n\n // Fallback for external absolute paths that couldn't be resolved with extensions\n return isExternal ? { path: fullPath, isExternal: true } : null;\n }\n\n /**\n * @description Checks whether `fullPath + ext` resolves to an existing file or an\n * `index` file inside `fullPath` as a directory.\n * @param fullPath - The candidate path without extension.\n * @param ext - Extension to append, including the dot (e.g. `\".ts\"`), or empty string to try as-is.\n * @param isExternal - Whether the path falls outside the project root.\n * @returns Resolved path and external flag, or `null` if neither variant exists.\n */\n private tryExtensions(fullPath: string, ext: string, isExternal: boolean): ResolvedImport | null {\n // Try file directly\n const candidatePath = fullPath + ext;\n if (this.isFile(candidatePath)) {\n return { path: candidatePath, isExternal };\n }\n\n // Try index file in directory (JS/TS convention: index.ts)\n const indexP = path.join(fullPath, `index${ext}`);\n if (this.isFile(indexP)) {\n return { path: indexP, isExternal };\n }\n\n // Python convention: __init__.py for packages\n if (ext === \".py\") {\n const initP = path.join(fullPath, \"__init__.py\");\n if (this.isFile(initP)) {\n return { path: initP, isExternal };\n }\n }\n\n return null;\n }\n\n /**\n * @description Safely checks whether a path refers to a regular file without throwing\n * on missing entries or permission errors.\n * @param filePath - Absolute path to test.\n * @returns `true` if the path exists and is a regular file.\n */\n private isFile(filePath: string): boolean {\n try {\n const stats = fs.statSync(filePath, { throwIfNoEntry: false });\n return stats?.isFile() === true;\n } catch {\n return false;\n }\n }\n\n /**\n * @description Reads `tsconfig.json` from the project root and attempts to match the\n * specifier against configured `compilerOptions.paths` aliases, trying each substitution\n * with multiple extensions.\n * @param specifier - The import specifier to match against path aliases.\n * @returns Resolved path and external flag if an alias matches, or `null` otherwise.\n */\n private resolvePathAlias(specifier: string): ResolvedImport | null {\n for (const searchDir of this.tsconfigSearchPaths) {\n const tsconfigPath = path.join(searchDir, \"tsconfig.json\");\n if (!fs.existsSync(tsconfigPath)) continue;\n\n try {\n const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, \"utf-8\"));\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) continue;\n\n for (const alias in paths) {\n const match = this.matchAliasPattern(alias, specifier);\n if (match) {\n const resolved = this.tryAliasSubstitutions(paths[alias], match[1] || \"\", searchDir);\n if (resolved) return resolved;\n }\n }\n } catch {\n // Ignore parse errors\n }\n }\n return null;\n }\n\n private aliasRegexCache = new Map<string, RegExp>();\n\n /**\n * @description Converts a tsconfig path alias (e.g. `\"@app/*\"`) to a regex and tests it\n * against the specifier, caching compiled regexes for repeated lookups.\n * @param alias - A tsconfig `paths` key, potentially containing a `*` wildcard.\n * @param specifier - The import specifier to test.\n * @returns The regex match array (including wildcard capture) if matched, or `null`.\n */\n private matchAliasPattern(alias: string, specifier: string): RegExpMatchArray | null {\n let regex = this.aliasRegexCache.get(alias);\n if (!regex) {\n const pattern = alias.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(\"\\\\*\", \"(.*)\");\n regex = new RegExp(`^${pattern}$`);\n this.aliasRegexCache.set(alias, regex);\n }\n return specifier.match(regex);\n }\n\n /**\n * @description Iterates over all substitution templates for a matched alias, replacing\n * the `*` placeholder with the captured wildcard segment, then probing for an existing file.\n * @param substitutions - The array of path templates from tsconfig `paths` (e.g. `[\"src/app/*\"]`).\n * @param wildcardMatch - The portion of the specifier that matched the `*` in the alias pattern.\n * @returns The first substitution that resolves to an existing file, or `null` if none match.\n */\n private tryAliasSubstitutions(\n substitutions: string[],\n wildcardMatch: string,\n baseDir: string = this.rootDir,\n ): ResolvedImport | null {\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\", \".coffee\", \".ls\", \".lua\", \".feature\"];\n\n for (const sub of substitutions) {\n const resolvedSub = sub.replace(\"*\", wildcardMatch);\n const fullPath = path.resolve(baseDir, resolvedSub);\n\n for (const ext of extensions) {\n const resolved = this.tryExtensions(fullPath, ext, false);\n if (resolved) return resolved;\n }\n }\n return null;\n }\n\n /**\n * @description Resolves a specifier against the workspace package map. Handles exact\n * package name matches and deep imports (`@myorg/shared/utils`). Resolved paths are\n * marked `isExternal: false` and `isWorkspace: true` so the builder treats them as\n * internal cross-package edges rather than npm dependencies.\n * @param {string} specifier - The raw import specifier to match against workspace package names.\n * @returns {ResolvedImport | null} Resolved path with workspace flags, or `null` if no package matches.\n */\n private resolveWorkspaceImport(specifier: string): ResolvedImport | null {\n if (this.workspaceMap.size === 0) return null;\n\n for (const [pkgName, pkgRoot] of this.workspaceMap) {\n if (specifier !== pkgName && !specifier.startsWith(`${pkgName}/`)) continue;\n\n const subPath = specifier.slice(pkgName.length); // \"\" or \"/deep/path\"\n const base: ResolvedImport = {\n path: \"\",\n isExternal: false,\n isWorkspace: true,\n workspacePackage: pkgName,\n };\n\n if (!subPath) {\n // Resolve to package entry — try common conventions\n for (const candidate of [\n \"src/index.ts\",\n \"src/index.tsx\",\n \"index.ts\",\n \"index.tsx\",\n \"index.js\",\n ]) {\n const abs = path.join(pkgRoot, candidate);\n try {\n if (fs.statSync(abs, { throwIfNoEntry: false })?.isFile()) {\n return { ...base, path: abs };\n }\n } catch {\n /* skip */\n }\n }\n // Fallback: package root itself (builder will handle gracefully)\n return { ...base, path: pkgRoot };\n }\n\n // Deep import: resolve subPath relative to pkgRoot\n const deepResolved = this.resolveLocalPath(path.join(pkgRoot, \"_dummy\"), subPath.slice(1));\n if (deepResolved) return { ...base, path: deepResolved.path };\n\n return null;\n }\n\n return null;\n }\n}\n","/** Language resolver for Go: maps module-local import paths to concrete .go files using go.mod. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\ninterface GoModData {\n /** Declared module path, e.g. `\"github.com/myorg/myrepo\"`. */\n mod: string | null;\n /**\n * `replace` directive map: module path (without version) → absolute local directory.\n * Only local-path replacements (`=> ./foo` or `=> /abs/path`) are recorded;\n * version-to-version redirects (`=> otherpkg v1.2.3`) are ignored.\n */\n replaces: Map<string, string>;\n}\n\n/**\n * @description Resolves Go module-local import paths to all concrete `.go` source files\n * in the target package directory, using `go.mod` for module name and `replace` directives.\n *\n * Two gaps addressed over the previous single-file resolver:\n * 1. Returns every non-test `.go` file in the package directory (one edge per file).\n * 2. Honours `replace` directives that redirect a module path to a local directory.\n *\n * Known remaining limitations (see ADR-007):\n * - Vendor directories are not traversed.\n * - `go.work` workspace files are not read.\n */\nexport class GoLangResolver implements LangResolver {\n extensions = [\".go\"];\n private goModCache = new Map<string, GoModData>();\n\n /**\n * @description Resolves a Go import specifier to all non-test `.go` files in the target\n * package directory. Returns `null` for stdlib, third-party, and root-module imports.\n * @param {string} _currentFile - Absolute path of the importing file (unused; resolution is module-relative).\n * @param {string} specifier - Full Go import path, e.g. `\"github.com/myorg/myrepo/internal/utils\"`.\n * @param {string} rootDir - Absolute project root where `go.mod` is located.\n * @param {Function} _resolveLocal - Generic resolver callback (unused for Go).\n * @returns {ResolvedImport[] | null} All non-test `.go` files in the package, or `null` if external/unresolvable.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n _resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const { mod, replaces } = this.readGoMod(rootDir);\n if (!mod) return null;\n\n // Check replace directives first — they can redirect any module path prefix.\n const redirected = this.applyReplace(specifier, replaces, rootDir);\n if (redirected !== undefined) {\n return goFilesInDir(redirected);\n }\n\n // Standard module-local resolution: specifier must start with the declared module name.\n if (specifier !== mod && !specifier.startsWith(`${mod}/`)) return null;\n\n const rel = specifier.slice(mod.length).replace(/^\\//, \"\");\n if (!rel) return null;\n\n return goFilesInDir(path.join(rootDir, rel));\n }\n\n /**\n * @description Reads and caches `go.mod` from the project root, extracting the module\n * name and any local `replace` directives.\n * @param {string} rootDir - Absolute directory containing `go.mod`.\n * @returns {GoModData} Parsed module data; `mod` is `null` when `go.mod` is absent or malformed.\n */\n private readGoMod(rootDir: string): GoModData {\n const cached = this.goModCache.get(rootDir);\n if (cached !== undefined) return cached;\n\n const empty: GoModData = { mod: null, replaces: new Map() };\n try {\n const content = fs.readFileSync(path.join(rootDir, \"go.mod\"), \"utf-8\");\n const data = parseGoMod(content, rootDir);\n this.goModCache.set(rootDir, data);\n return data;\n } catch {\n this.goModCache.set(rootDir, empty);\n return empty;\n }\n }\n\n /**\n * @description Checks whether the specifier matches any `replace` directive and returns\n * the absolute local directory it maps to, or `undefined` if no match.\n *\n * A replace directive `replace A => ./local` matches specifier `A/pkg/sub`\n * and maps it to `<rootDir>/local/pkg/sub`.\n * @param {string} specifier - The import path to check.\n * @param {Map<string, string>} replaces - Parsed replace map: module prefix → absolute dir.\n * @param {string} rootDir - Project root, used when replacing relative paths.\n * @returns {string | undefined} Absolute target directory, or `undefined` if no directive matches.\n */\n private applyReplace(\n specifier: string,\n replaces: Map<string, string>,\n rootDir: string,\n ): string | undefined {\n for (const [from, toDir] of replaces) {\n if (specifier === from) {\n return toDir;\n }\n if (specifier.startsWith(`${from}/`)) {\n const sub = specifier.slice(from.length + 1);\n return path.join(toDir, sub);\n }\n }\n // Unused parameter kept to avoid signature drift — rootDir is used during parsing.\n void rootDir;\n return undefined;\n }\n}\n\n/**\n * @description Parses a `go.mod` file and extracts the declared module name and all\n * local-path `replace` directives.\n *\n * Handles both block form (`replace ( ... )`) and single-line form (`replace A => B`).\n * Only records directives whose replacement target is a relative or absolute local path\n * (starts with `.` or `/`). Version-to-version redirects are skipped.\n * @param {string} content - Raw text of `go.mod`.\n * @param {string} rootDir - Project root, used to resolve relative replacement paths.\n * @returns {GoModData} Parsed module name and replace map.\n */\nfunction parseGoMod(content: string, rootDir: string): GoModData {\n const lines = content.split(\"\\n\");\n let mod: string | null = null;\n const replaces = new Map<string, string>();\n\n let inReplaceBlock = false;\n\n for (const raw of lines) {\n const line = raw.trim();\n\n if (line.startsWith(\"module \")) {\n mod = line.slice(\"module \".length).trim();\n continue;\n }\n\n // Block open: `replace (`\n if (/^replace\\s*\\(/.test(line)) {\n inReplaceBlock = true;\n continue;\n }\n\n // Block close\n if (inReplaceBlock && line === \")\") {\n inReplaceBlock = false;\n continue;\n }\n\n // Line inside a replace block, e.g. `github.com/org/repo => ../local`\n if (inReplaceBlock && line.includes(\"=>\")) {\n parseReplaceLine(line, rootDir, replaces);\n continue;\n }\n\n // Single-line replace: `replace github.com/org/repo => ../local`\n if (!inReplaceBlock && /^replace\\s+/.test(line) && line.includes(\"=>\")) {\n parseReplaceLine(line.replace(/^replace\\s+/, \"\"), rootDir, replaces);\n }\n }\n\n return { mod, replaces };\n}\n\n/**\n * @description Parses a single replace directive line (without the leading `replace` keyword)\n * and records it in `out` when the target is a local path.\n *\n * Line forms:\n * - `github.com/org/repo => ./local`\n * - `github.com/org/repo v1.0.0 => ./local`\n * - `github.com/org/repo => /absolute/path`\n *\n * Version-to-version targets (`=> other/module v1.2.3`) are ignored.\n * @param {string} line - Trimmed directive text after stripping the `replace` keyword.\n * @param {string} rootDir - Project root for resolving relative replacement paths.\n * @param {Map<string, string>} out - Map to populate with resolved replacements.\n */\nfunction parseReplaceLine(line: string, rootDir: string, out: Map<string, string>): void {\n const [lhs, rhs] = line.split(\"=>\").map((side) => side.trim());\n if (!lhs || !rhs) return;\n\n // Strip optional version from lhs: `github.com/org/repo v1.0.0` → `github.com/org/repo`\n const fromModule = lhs.split(/\\s+/)[0] as string;\n\n // Only handle local path targets (relative or absolute)\n if (!rhs.startsWith(\".\") && !rhs.startsWith(\"/\")) return;\n\n const absTarget = path.isAbsolute(rhs) ? rhs : path.resolve(rootDir, rhs);\n out.set(fromModule, absTarget);\n}\n\n/**\n * @description Returns all non-test `.go` files in a package directory as resolved imports.\n * Files ending in `_test.go` are excluded — they are discovered separately by the builder's\n * test-file scan and should not appear as dependency targets.\n * @param {string} absDir - Absolute path to the Go package directory.\n * @returns {ResolvedImport[] | null} Array of resolved imports, or `null` if the directory\n * is missing or contains no non-test Go files.\n */\nfunction goFilesInDir(absDir: string): ResolvedImport[] | null {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(absDir, { withFileTypes: true });\n } catch {\n return null;\n }\n\n const files = entries\n .filter(\n (dirent) =>\n dirent.isFile() && dirent.name.endsWith(\".go\") && !dirent.name.endsWith(\"_test.go\"),\n )\n .map((dirent): ResolvedImport => ({ path: path.join(absDir, dirent.name), isExternal: false }))\n .sort((resolvedA, resolvedB) => resolvedA.path.localeCompare(resolvedB.path));\n\n return files.length > 0 ? files : null;\n}\n","/** Language resolver for Lua: converts dot-separated module names to local .lua file paths. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\n/**\n * @description Resolves Lua dot-separated module names (e.g. `utils.string`) to local files\n * by converting dots to path separators and probing the project root and a `lib/` sub-directory.\n */\nexport class LuaLangResolver implements LangResolver {\n extensions = [\".lua\"];\n\n /**\n * @description Converts dots in the specifier to path separators and probes the project root\n * and a `lib/` subdirectory using the generic resolver's extension-probing logic.\n * @param {string} _currentFile - Absolute path of the importing file (unused; search is root-relative).\n * @param {string} specifier - Dot-separated Lua module name, e.g. `\"utils.string\"`.\n * @param {string} rootDir - Absolute project root used as the primary search base.\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport | null} Local file path, or `null` if no match is found.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const luaSpecifier = specifier.replace(/\\./g, path.sep);\n const searchBases = [rootDir, path.join(rootDir, \"lib\")];\n\n for (const base of searchBases) {\n if (!fs.existsSync(base)) continue;\n const resolved = resolveLocal(path.join(base, \"_dummy.lua\"), luaSpecifier);\n if (resolved) return [resolved];\n }\n\n return null;\n }\n}\n","/** Language resolver for Python: maps bare module specifiers to local .py files or __init__.py packages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\n/**\n * @description Resolves bare Python module names (e.g. `mymodule` or `mypackage.sub`)\n * to local `.py` files or `__init__.py` packages inside the project root.\n * Dots in the specifier are treated as path separators.\n */\nexport class PythonLangResolver implements LangResolver {\n extensions = [\".py\"];\n\n /**\n * @description Converts dots in the specifier to path separators and probes for a matching\n * `.py` file or `__init__.py` package relative to the project root.\n * @param {string} _currentFile - Absolute path of the importing file (unused; resolution is root-relative).\n * @param {string} specifier - Bare module name, e.g. `\"mypackage.sub\"`.\n * @param {string} rootDir - Absolute project root used as the search base.\n * @param {Function} _resolveLocal - Generic resolver callback (unused for Python).\n * @returns {ResolvedImport[] | null} Single-element array with the local file, or `null` if no match is found.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n _resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const pyPath = specifier.replace(/\\./g, path.sep);\n\n const pyFile = path.join(rootDir, `${pyPath}.py`);\n if (isFile(pyFile)) return [{ path: pyFile, isExternal: false }];\n\n const initFile = path.join(rootDir, pyPath, \"__init__.py\");\n if (isFile(initFile)) return [{ path: initFile, isExternal: false }];\n\n return null;\n }\n}\n\n/**\n * @description Safely checks whether a path refers to a regular file without throwing on missing entries.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a regular file.\n */\nfunction isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() === true;\n } catch {\n return false;\n }\n}\n","/** Proposes semantic test tags and affected test files from git diff and the dependency graph. */\n\nimport type { Graph } from \"../graph\";\nimport {\n detectFeatures,\n type FeatureDetectionOptions,\n type FeatureInfo,\n SymbolTraversalContext,\n} from \"../graph\";\nimport type { FileNode } from \"../types/node\";\nimport { DefaultTestNodeIdentifier, type TestNodeIdentifier } from \"./identifier\";\n\n/** @description Options for `proposeTags` and `proposeAffectedTests`, allowing callers to override the test-node identifier and feature-detection behaviour. */\nexport interface ProposeTagsOptions {\n identifier?: TestNodeIdentifier;\n featureDetection?: FeatureDetectionOptions | false;\n}\n\n/**\n * @description Materialises optional settings into concrete implementations.\n *\n * The feature map is computed once here so traversal can do O(1) hub lookups\n * rather than re-running detection on every visited node.\n * @param {Graph} graph - The full project dependency graph, needed to run feature detection.\n * @param {ProposeTagsOptions} [options] - Optional identifier and feature-detection overrides.\n * @returns {{ identifier: TestNodeIdentifier; featureMap: Map<string, FeatureInfo> }} Concrete identifier and pre-computed feature map ready for traversal.\n */\nfunction resolveOptions(\n graph: Graph,\n options?: ProposeTagsOptions,\n): { identifier: TestNodeIdentifier; featureMap: Map<string, FeatureInfo> } {\n return {\n identifier: options?.identifier ?? new DefaultTestNodeIdentifier(),\n featureMap:\n options?.featureDetection === false\n ? new Map()\n : detectFeatures(graph.nodes, options?.featureDetection ?? undefined),\n };\n}\n\n/**\n * @description Walks the incoming dependency graph from each changed file.\n *\n * For every reachable node that passes the symbol-propagation check:\n * - If the node is a feature hub (and not the start node), `onFeatureHub` is\n * called and that branch is pruned — preventing traversal explosions.\n * - Otherwise `onNode` is called so callers can decide what to collect.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified.\n * @param {Map<string, FeatureInfo>} featureMap - Pre-computed map of path → feature hub info.\n * @param {(feature: FeatureInfo) => void} onFeatureHub - Called when a feature hub is encountered; return signals pruning.\n * @param {(node: FileNode) => void} onNode - Called for every non-hub reachable node that passes the symbol check.\n */\nfunction traverseAffected(\n graph: Graph,\n changedFiles: string[],\n featureMap: Map<string, FeatureInfo>,\n onFeatureHub: (feature: FeatureInfo) => void,\n onNode: (node: FileNode) => void,\n): void {\n for (const changed of changedFiles) {\n const startNode = graph.nodes.get(changed);\n if (!startNode) continue;\n\n const context = new SymbolTraversalContext(changed, [\n \"*\",\n ...startNode.exports.map((exportedSym) => exportedSym.name),\n ]);\n\n graph.traverse(\n changed,\n (visitedNode, depth, childPath) => {\n if (!childPath) return true; // start node — always continue\n\n if (!context.updateAffectedSymbols(visitedNode, childPath)) return false;\n\n if (depth > 0) {\n const feature = featureMap.get(visitedNode.path);\n if (feature) {\n onFeatureHub(feature);\n return false; // prune: don't walk past this hub\n }\n }\n\n onNode(visitedNode);\n return true;\n },\n { direction: \"incoming\" },\n );\n }\n}\n\n/**\n * @description Proposes Vitest tags to run based on which files changed.\n *\n * Traverses the incoming dependency graph from each changed file. Test nodes\n * that can reach the changed file contribute their tags. Feature hubs act as\n * boundaries: the hub's tag is emitted and traversal stops there, preventing\n * combinatorial blowup in large graphs.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified (e.g. from git diff).\n * @param {ProposeTagsOptions} [options] - Optional: custom test identifier and feature-detection settings.\n * @returns {string[]} Deduplicated list of tag strings to pass to `vitest --grep`.\n */\nexport function proposeTags(\n graph: Graph,\n changedFiles: string[],\n options?: ProposeTagsOptions,\n): string[] {\n const { identifier, featureMap } = resolveOptions(graph, options);\n const proposedTags = new Set<string>();\n\n // A changed file that is itself a feature hub should immediately emit its tag\n // (it won't appear during incoming traversal since traversal starts from it).\n for (const changed of changedFiles) {\n const feature = featureMap.get(changed);\n if (feature) proposedTags.add(feature.tag);\n }\n\n traverseAffected(\n graph,\n changedFiles,\n featureMap,\n (feature) => proposedTags.add(feature.tag),\n (node) => {\n if (identifier.isTestNode(node)) {\n for (const tag of node.tags) proposedTags.add(tag.name);\n }\n },\n );\n\n return Array.from(proposedTags);\n}\n\n/**\n * @description Returns the file paths of test files affected by the changed files.\n *\n * Traverses the incoming dependency graph from each changed file and collects\n * paths of reachable test nodes. Feature hubs act as traversal boundaries —\n * tests beyond a hub are excluded because the hub's own tag already covers\n * them when using `proposeTags`.\n *\n * The output is a plain list of relative paths suitable for piping directly\n * into Vitest: `vitest $(mokosh --affected-tests)`.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified (e.g. from git diff).\n * @param {ProposeTagsOptions} [options] - Optional: custom test identifier and feature-detection settings.\n * @returns {string[]} Deduplicated list of relative test file paths.\n */\nexport function proposeAffectedTests(\n graph: Graph,\n changedFiles: string[],\n options?: ProposeTagsOptions,\n): string[] {\n const { identifier, featureMap } = resolveOptions(graph, options);\n const affectedTests = new Set<string>();\n\n traverseAffected(\n graph,\n changedFiles,\n featureMap,\n () => {}, // feature hubs don't contribute test paths — their sub-graph stays pruned\n (node) => {\n if (identifier.isTestNode(node)) {\n affectedTests.add(node.path);\n }\n },\n );\n\n return Array.from(affectedTests);\n}\n","/** Public library API: createImportMap, createWorkspaceGraph, and getAllProjectFiles. */\n\n// Config\nexport { applyConfig, loadMokoshConfig, type MokoshConfig } from \"./config\";\n\n// Constants\nexport { DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS, type ScanOptions } from \"./const\";\n\n// Coverage\nexport { loadCoverageMap } from \"./coverage\";\n\n// Exporters\nexport { type GraphExporter, MermaidExporter, toMermaid } from \"./exporters\";\n// Graph analysis utilities\nexport {\n type ApiSurface,\n buildApiSurface,\n detectAllEntryPoints,\n detectEntryPoint,\n type ExportKind,\n type PublicExport,\n} from \"./graph/api-surface\";\nexport { queryCallGraph } from \"./graph/call-graph\";\nexport type {\n CalleeEntry,\n CallerEntry,\n FunctionCallInfo,\n} from \"./graph/call-graph/types\";\nexport {\n buildChangeImpactCache,\n type ChangeImpactCache,\n computeGraphHash,\n isChangeImpactCacheValid,\n loadChangeImpactCache,\n queryChangeImpact,\n saveChangeImpactCache,\n} from \"./graph/change-impact-cache\";\nexport {\n detectFeatures,\n type FeatureDetectionOptions,\n type FeatureInfo,\n} from \"./graph/features\";\nexport {\n buildFeatureGraph,\n type FeatureDomain,\n type FeatureGraph,\n type FeatureGraphOptions,\n} from \"./graph/features/feature-graph\";\n// Core graph classes\nexport { Graph } from \"./graph/model\";\nexport { buildResponsibilityGraph } from \"./graph/responsibility\";\nexport type {\n ModuleResponsibility,\n ModuleRole,\n ResponsibilityGraph,\n} from \"./graph/responsibility/types\";\nexport { SymbolTraversalContext } from \"./graph/symbol-traversal\";\nexport {\n buildTypeGraph,\n queryTypeGraph,\n type TypeEdge,\n type TypeGraph,\n type TypeKind,\n type TypeNode,\n type TypeQueryResult,\n} from \"./graph/type-graph\";\n// Monorepo detection + extension point\nexport { detectMonorepo } from \"./graph/workspace\";\nexport { type MonorepoDetector, registerMonorepoDetector } from \"./graph/workspace/registry\";\nexport type { MonorepoLayout, WorkspacePackage } from \"./graph/workspace/types\";\nexport { type SerializedWorkspaceGraph, WorkspaceGraph } from \"./graph/workspace-model\";\nexport {\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n} from \"./parser/classify\";\n// Parser extension points\nexport { registerParser } from \"./parser/registry\";\n// Query\nexport { filterGraph, type NodeQuery, parseQuery } from \"./query\";\n// Tags\nexport {\n type ApplyTagsFileResult,\n type ApplyTagsResult,\n applyTags,\n type ProposeTagsOptions,\n proposeAffectedTests,\n proposeTags,\n type TestNodeIdentifier,\n} from \"./tags\";\nexport type {\n DependencyGraph,\n SerializedGraph,\n TraversalOptions,\n TraversalVisitor,\n} from \"./types/graph\";\n// Core data types\nexport type {\n CallEdge,\n ExportedSymbol,\n FileNode,\n ImportEdge,\n StructuredTag,\n} from \"./types/node\";\nexport type { FileType, ImportType, NodeCategory, TagKind } from \"./types/parse\";\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS, type ScanOptions } from \"./const\";\nimport { DefaultResolver, detectMonorepo, type Graph, GraphBuilder, WorkspaceGraph } from \"./graph\";\n\n/**\n * @description Builds a dependency graph from the given entry points, optionally reusing a\n * previously built graph for incremental updates.\n * @param rootDir - Absolute or relative path to the project root; resolved internally.\n * @param entryPoints - File paths (relative to `rootDir`) that seed the graph walk.\n * @param previousGraph - An earlier graph to diff against for incremental builds; pass `null` for a full build.\n * @param options - `silent` suppresses progress output; `gitStats` attaches git churn data; `coverageMap` maps file paths to line-coverage percentages.\n * @returns The fully-built Graph with all reachable nodes and import edges populated.\n */\nexport async function createImportMap(\n rootDir: string,\n entryPoints: string[],\n previousGraph: Graph | null = null,\n options: { silent?: boolean; gitStats?: boolean; coverageMap?: Map<string, number> } = {},\n): Promise<Graph> {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`Processed ${count} files...\\r`);\n };\n const builder = new GraphBuilder(\n path.resolve(rootDir),\n previousGraph,\n undefined,\n progressCallback,\n options.gitStats ?? false,\n options.coverageMap ?? new Map(),\n );\n return await builder.build(entryPoints);\n}\n\n/**\n * @description Auto-detects the monorepo layout under `rootDir` and builds a per-package\n * dependency graph, stitching them together into a single WorkspaceGraph.\n * @param rootDir - Absolute path to the monorepo root.\n * @param options - `packages` filters to a named subset of packages; `silent` suppresses progress; `gitStats` attaches git churn data per file.\n * @returns A WorkspaceGraph where each package has its own Graph and cross-package edges are resolved.\n */\nexport async function createWorkspaceGraph(\n rootDir: string,\n options: { packages?: string[]; silent?: boolean; gitStats?: boolean } = {},\n): Promise<WorkspaceGraph> {\n const abs = path.resolve(rootDir);\n const layout = detectMonorepo(abs);\n\n const pkgs = options.packages\n ? layout.packages.filter(\n (pkg) =>\n options.packages?.includes(pkg.name) || options.packages?.includes(pkg.relativeRoot),\n )\n : layout.packages;\n\n const workspaceMap = new Map(layout.packages.map((pkg) => [pkg.name, pkg.root]));\n const wg = new WorkspaceGraph(abs, layout.type);\n\n for (const pkg of pkgs) {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`[${pkg.name}] Processed ${count} files...\\r`);\n };\n const builder = new GraphBuilder(\n abs,\n null,\n new DefaultResolver(abs, { workspaceMap, tsconfigSearchPaths: [pkg.root, abs] }),\n progressCallback,\n options.gitStats ?? false,\n );\n const graph = await builder.build(pkg.entryPoints);\n wg.addPackage(pkg, graph);\n }\n\n return wg;\n}\n\n/**\n * @description Recursively walks `rootDir` and returns paths of every file whose extension\n * is in the allowed set, skipping ignored directories. Silently skips unreadable entries.\n * @param rootDir - Root directory to scan; returned paths are relative to this.\n * @param options - Override or extend the default ignore-dir and extension lists via ScanOptions.\n * @returns Relative file paths for all matching source files found under `rootDir`.\n */\nexport function getAllProjectFiles(rootDir: string, options: ScanOptions = {}): string[] {\n const files: string[] = [];\n const ignoreDirs = new Set([\n ...(options.ignoreDirs ?? DEFAULT_IGNORE_DIRS),\n ...(options.additionalIgnoreDirs ?? []),\n ]);\n const extensions = new Set([\n ...(options.extensions ?? DEFAULT_EXTENSIONS),\n ...(options.additionalExtensions ?? []),\n ]);\n\n /**\n * @description Recursively visits `dir`, pushing matching file paths into the outer `files` array.\n * @param dir - Absolute path of the directory to scan in this recursion step.\n */\n function walk(dir: string) {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!ignoreDirs.has(entry.name)) {\n walk(fullPath);\n }\n } else if (entry.isFile()) {\n if (extensions.has(path.extname(entry.name).toLowerCase())) {\n files.push(path.relative(rootDir, fullPath));\n }\n }\n }\n } catch (_e) {\n // Permission issues or broken symlinks\n }\n }\n\n walk(rootDir);\n return files;\n}\n","import path from \"node:path\";\nimport { DefaultGitProvider } from \"../git\";\nimport {\n applyConfig,\n applyTags,\n buildApiSurface,\n buildFeatureGraph,\n buildResponsibilityGraph,\n buildTypeGraph,\n detectAllEntryPoints,\n detectFeatures,\n detectMonorepo,\n filterGraph,\n Graph,\n getAllProjectFiles,\n loadCoverageMap,\n loadMokoshConfig,\n MermaidExporter,\n parseQuery,\n proposeAffectedTests,\n proposeTags,\n queryCallGraph,\n queryChangeImpact,\n queryTypeGraph,\n SymbolTraversalContext,\n} from \"../index\";\nimport type { SessionState } from \"./cache\";\nimport type { TextResponse } from \"./utils\";\nimport { text } from \"./utils\";\n\n// ---------------------------------------------------------------------------\n// Argument types — one per tool, matching the schemas defined in tools.ts\n// ---------------------------------------------------------------------------\n\nexport type AnalyzeArgs = { root: string; entryPoints: string[] };\nexport type GetWorkspacePackagesArgs = { root: string };\nexport type GetWorkspaceAffectedArgs = { root: string; file: string };\nexport type GetDependenciesArgs = { root: string; file: string; depth?: number };\nexport type GetDependentsArgs = { root: string; file: string };\nexport type GetAffectedArgs = {\n root: string;\n file: string;\n testsOnly?: boolean;\n cached?: boolean;\n changedSymbols?: string[];\n};\nexport type GetCallersArgs = {\n root: string;\n file: string;\n depth?: number;\n withEdgeDetail?: boolean;\n};\nexport type FindUnusedArgs = { root: string; entryPoints: string[] };\nexport type FindUncoveredArgs = { root: string; coverageThreshold?: number };\nexport type FindComplexFunctionsArgs = {\n root: string;\n metric?: \"cognitiveComplexity\" | \"complexity\";\n threshold?: number;\n limit?: number;\n};\nexport type ProposeTagsArgs = {\n root: string;\n changedFiles?: string[];\n featureThreshold?: number;\n format?: \"tags\" | \"paths\";\n};\nexport type DetectFeaturesArgs = {\n root: string;\n entryPoints?: string[];\n featureThreshold?: number;\n};\nexport type QueryArgs = {\n root: string;\n entryPoints?: string[];\n filter: string;\n mermaid?: boolean;\n slim?: boolean;\n};\n\nexport type ClearCacheArgs = { root: string };\nexport type GetTypeGraphArgs = { root: string; type?: string };\nexport type GetModuleResponsibilityArgs = { root: string; paths?: string[]; minOutDegree?: number };\nexport type GetFeatureGraphArgs = { root: string; minOutDegree?: number };\nexport type GetCallGraphArgs = { root: string; function: string };\nexport type GetApiSurfaceArgs = { root: string; entryPoints?: string[] };\nexport type ApplyTagsArgs = { root: string; dryRun?: boolean };\n\nexport type ToolArgs =\n | AnalyzeArgs\n | GetDependenciesArgs\n | GetDependentsArgs\n | GetAffectedArgs\n | GetCallersArgs\n | FindUnusedArgs\n | FindUncoveredArgs\n | FindComplexFunctionsArgs\n | ProposeTagsArgs\n | DetectFeaturesArgs\n | QueryArgs\n | ClearCacheArgs\n | GetTypeGraphArgs\n | GetModuleResponsibilityArgs\n | GetFeatureGraphArgs\n | GetCallGraphArgs\n | GetApiSurfaceArgs\n | ApplyTagsArgs;\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * @description Builds (or incrementally refreshes) the dependency graph and caches it for\n * the session. When `entryPoints` is empty, auto-detects whether `root` is a monorepo\n * and builds a per-package workspace graph if so.\n * @param cache - Session state used to store and retrieve the built graph.\n * @param args - `root` is the project directory; `entryPoints` seeds the graph walk (empty triggers monorepo auto-detect).\n * @returns A lightweight summary of node count, categories, and cycles — call `get_dependencies` or `query` for full graph data.\n */\nexport async function handleAnalyze(cache: SessionState, args: AnalyzeArgs) {\n const { root, entryPoints } = args;\n if (!cache.isConfigured(root)) {\n const config = loadMokoshConfig(root, { allowJs: false });\n applyConfig(config);\n cache.storeConfig(root, config);\n }\n\n // Auto-detect monorepo when no entry points are provided\n if (entryPoints.length === 0) {\n const layout = detectMonorepo(root);\n if (layout.type !== \"none\") {\n const config = cache.getConfig(root);\n const wg = await cache.getOrBuildWorkspace(root, { gitStats: config?.gitStats ?? false });\n cache.storeLastAnalyze(root, { kind: \"workspace\" });\n cache.startWatching(root);\n const perPackage = Array.from(wg.packages.values()).map(({ graph, pkg }) => ({\n package: pkg.name,\n relativeRoot: pkg.relativeRoot,\n nodeCount: graph.nodes.size,\n }));\n return text({\n monorepoType: layout.type,\n packageCount: wg.packages.size,\n packages: perPackage,\n });\n }\n }\n\n const resolvedEntries = entryPoints.map((ep) => path.resolve(root, ep));\n const config = cache.getConfig(root);\n const coverageMap = config?.coverageReportPath\n ? loadCoverageMap(root, config.coverageReportPath)\n : new Map<string, number>();\n const graph = await cache.getOrBuild(root, resolvedEntries, coverageMap);\n cache.storeLastAnalyze(root, { kind: \"single\", entryPoints: resolvedEntries, coverageMap });\n cache.startWatching(root);\n const serialized = graph.serialize();\n const categories = serialized.nodes.reduce<Record<string, number>>((acc, node) => {\n acc[node.category] = (acc[node.category] ?? 0) + 1;\n return acc;\n }, {});\n const cycles = graph.findCycles();\n return text({ nodeCount: serialized.nodes.length, categories, cycles });\n}\n\n/**\n * @description Outgoing traversal from `file` — returns all files that `file` imports,\n * up to `depth` hops (default 1 = immediate imports only). Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `file` is the starting node; `depth` caps traversal depth.\n * @returns TextResponse with `{ file, dependencies }` listing all reachable imported paths.\n */\nexport async function handleGetDependencies(\n cache: SessionState,\n args: GetDependenciesArgs,\n): Promise<TextResponse> {\n const { root, file, depth = 1 } = args;\n const graph = await cache.ensureFresh(root);\n const deps: Array<{ path: string; symbols?: string[] }> = [];\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 text({ file, dependencies: deps });\n}\n\n/**\n * @description Incoming one-hop traversal — returns files that directly import `file`.\n * For the full transitive upstream set use `handleGetAffected` instead. Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `file` is the node whose direct importers to find.\n * @returns TextResponse with `{ file, dependents }` listing files that import `file` directly.\n */\nexport async function handleGetDependents(\n cache: SessionState,\n args: GetDependentsArgs,\n): Promise<TextResponse> {\n const { root, file } = args;\n const graph = await cache.ensureFresh(root);\n const dependents: Array<{ path: string; symbols?: string[] }> = [];\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 text({ file, dependents });\n}\n\n/**\n * @description Full incoming traversal from `file` upward — returns every file whose behaviour\n * could change if `file` changes (blast-radius analysis). Set `cached=true` to use a pre-computed\n * O(1) impact cache instead of graph traversal — faster on repeated calls for the same root.\n * Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `file` is the changed node; `testsOnly` restricts results to test/spec files; `cached` switches to the impact cache.\n * @returns TextResponse with `{ file, affected, count }` listing all transitively impacted files.\n */\nexport async function handleGetAffected(\n cache: SessionState,\n args: GetAffectedArgs,\n): Promise<TextResponse> {\n const { root, file, testsOnly = false, cached = false, changedSymbols } = args;\n const graph = await cache.ensureFresh(root);\n if (cached) {\n const impactCache = cache.getOrBuildChangeImpact(root);\n const allAffected = queryChangeImpact(impactCache, file);\n const affected = testsOnly\n ? allAffected.filter((filePath) => graph.nodes.get(filePath)?.category === \"test\")\n : allAffected;\n return text({ file, affected, count: affected.length });\n }\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 text({ file, affected, count: affected.length });\n}\n\n/**\n * @description Incoming call-edge traversal — returns files whose exported functions call\n * into `file`. More precise than `handleGetAffected` because it follows runtime call edges\n * rather than all import edges.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root`/`file` identify the target; `depth` caps hops; `withEdgeDetail` adds from/to function names per edge.\n * @returns TextResponse with `{ file, callers, count }` where each caller optionally includes edge detail.\n */\nexport async function handleGetCallers(\n cache: SessionState,\n args: GetCallersArgs,\n): Promise<TextResponse> {\n const { root, file, depth = 1, withEdgeDetail = false } = args;\n const graph = await cache.ensureFresh(root);\n const callers: Array<{ file: string; edges?: Array<{ from: string; to: string }> }> = [];\n graph.traverseCalls(\n file,\n (node) => {\n if (node.path === file) return true;\n const entry: { file: string; edges?: Array<{ from: string; to: string }> } = {\n file: node.path,\n };\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 text({ file, callers, count: callers.length });\n}\n\n/**\n * @description Scans the entire project directory and compares against the graph reachable\n * from `entryPoints`, returning files that exist on disk but are never imported — candidates for deletion.\n * @param cache - Session state used to build or retrieve the graph.\n * @param args - `root` is the project directory; `entryPoints` seeds the reachability walk.\n * @returns TextResponse with `{ unusedFiles, count }` listing files unreachable from any entry point.\n */\nexport async function handleFindUnused(cache: SessionState, args: FindUnusedArgs) {\n const { root, entryPoints } = args;\n const resolvedEntries = entryPoints.map((ep) => path.resolve(root, ep));\n const graph = await cache.getOrBuild(root, resolvedEntries);\n const allFiles = getAllProjectFiles(root);\n const unusedFiles = graph.findUnusedFiles(allFiles);\n return text({ unusedFiles, count: unusedFiles.length });\n}\n\n/**\n * @description Returns non-test files whose line coverage is below the configured threshold.\n * Threshold priority: `args.coverageThreshold` → `config.coverageThreshold` → 80.\n * Requires a prior `analyze` call with `coverageReportPath` set in `mokosh.config`.\n * Returns an error when no coverage data was loaded rather than treating all files as 0%.\n * @param cache - Session state holding the cached graph and config.\n * @param args - `root` selects the graph; `coverageThreshold` overrides the config default.\n * @returns TextResponse with `{ threshold, uncovered, count }` where each entry includes file path and coverage percentage.\n */\nexport async function handleFindUncovered(\n cache: SessionState,\n args: FindUncoveredArgs,\n): Promise<TextResponse> {\n const { root, coverageThreshold } = args;\n const graph = await cache.ensureFresh(root);\n const config = cache.getConfig(root);\n const threshold = coverageThreshold ?? config?.coverageThreshold ?? 80;\n\n const hasCoverageData = [...graph.nodes.values()].some((node) => node.coveragePct !== undefined);\n if (!hasCoverageData) {\n return text({\n error:\n \"No coverage data available. Set coverageReportPath in mokosh.config and call analyze again.\",\n });\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 return text({ threshold, uncovered, count: uncovered.length });\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. TypeScript/JavaScript only — files\n * without a `functions` breakdown contribute no results.\n * @param cache - Session state holding the cached graph.\n * @param args - `root` selects the graph; `metric` picks which score to threshold/sort on\n * (default `cognitiveComplexity`); `threshold` is the minimum score to include (default 10);\n * `limit` caps the number of results returned (default 20).\n * @returns TextResponse with `{ metric, threshold, functions, count }`.\n */\nexport async function handleFindComplexFunctions(\n cache: SessionState,\n args: FindComplexFunctionsArgs,\n): Promise<TextResponse> {\n const { root, metric = \"cognitiveComplexity\", threshold = 10, limit = 20 } = args;\n const graph = await cache.ensureFresh(root);\n\n const functions = [...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 return text({ metric, threshold, functions, count: functions.length });\n}\n\n/**\n * @description Backward-traverses from each changed file to propose what to run.\n * format='tags' (default) collects tags from transitively dependent test files for CI tag-filtering.\n * format='paths' returns test file paths ready to pipe to a test runner.\n * Feature hub files short-circuit traversal and emit a `feature:<name>` tag to prevent explosion.\n * Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `changedFiles` overrides git diff detection; `featureThreshold` tunes hub sensitivity; `format` controls output shape.\n * @returns TextResponse with `{ changedFiles, proposedTags }` for tags or `{ changedFiles, affectedTests, count }` for paths.\n */\nexport async function handleProposeTags(\n cache: SessionState,\n args: ProposeTagsArgs,\n): Promise<TextResponse> {\n const { root, changedFiles, featureThreshold, format = \"tags\" } = args;\n const graph = await cache.ensureFresh(root);\n const files =\n changedFiles ??\n new DefaultGitProvider()\n .getChangedFiles()\n .map((filePath) => path.relative(root, path.resolve(root, filePath)));\n const opts =\n featureThreshold !== undefined\n ? { featureDetection: { minOutDegree: featureThreshold } }\n : undefined;\n if (format === \"paths\") {\n const affectedTests = proposeAffectedTests(graph, files, opts);\n return text({ changedFiles: files, affectedTests, count: affectedTests.length });\n }\n const tags = proposeTags(graph, files, opts);\n return text({ changedFiles: files, proposedTags: tags });\n}\n\n/**\n * @description Identifies feature hub files — source files with high out-degree (many imports) —\n * sorted by out-degree descending. Builds from `entryPoints` when provided, else reuses the cached graph.\n * @param cache - Session state used to build or retrieve the graph.\n * @param args - `root` is the project directory; `entryPoints` optionally seeds a fresh build; `featureThreshold` sets the minimum out-degree to qualify.\n * @returns TextResponse with `{ features, count }` sorted by out-degree descending.\n */\nexport async function handleDetectFeatures(\n cache: SessionState,\n args: DetectFeaturesArgs,\n): Promise<TextResponse> {\n const { root, entryPoints, featureThreshold } = args;\n const graph = entryPoints\n ? await cache.getOrBuild(\n root,\n entryPoints.map((ep) => path.resolve(root, ep)),\n )\n : await cache.ensureFresh(root);\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 return text({ features, count: features.length });\n}\n\n/**\n * @description Filters the graph by category, tag, or path substring and returns matching nodes.\n * Slim mode (default) strips edge metadata and internal tags for a compact response; pass `slim: false` for full edge data.\n * @param cache - Session state used to build or retrieve the graph.\n * @param args - `root`/`entryPoints` select the graph; `filter` is the query DSL string; `mermaid` switches output to a diagram; `slim` controls response verbosity.\n * @returns TextResponse containing either a Mermaid diagram string or a JSON node list with cycle info.\n */\nexport async function handleQuery(cache: SessionState, args: QueryArgs): Promise<TextResponse> {\n const { root, entryPoints, filter, mermaid = false, slim = true } = args;\n const graph = entryPoints\n ? await cache.getOrBuild(\n root,\n entryPoints.map((ep) => path.resolve(root, ep)),\n )\n : await cache.ensureFresh(root);\n const filtered = filterGraph(graph.serialize(), parseQuery(filter));\n if (mermaid) {\n return text(MermaidExporter.serialize(Graph.deserialize(filtered)));\n }\n if (slim) {\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 text({ nodes: slimNodes, cycles: filtered.cycles });\n }\n return text(filtered);\n}\n\n/**\n * @description Lists all workspace packages detected in a monorepo root, including per-package\n * node counts and cross-package dependency edges. Requires a prior `analyze` call with no entry points.\n * @param cache - Session state holding the cached WorkspaceGraph.\n * @param args - `root` identifies the monorepo root to look up.\n * @returns TextResponse with `{ monorepoType, packageCount, packages }` where each package includes its dependsOn list.\n */\nexport async function handleGetWorkspacePackages(\n cache: SessionState,\n args: GetWorkspacePackagesArgs,\n): Promise<TextResponse> {\n const { root } = args;\n const wg = await cache.ensureFreshWorkspace(root);\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 text({ monorepoType: wg.type, packageCount: packages.length, packages });\n}\n\n/**\n * @description Cross-package blast-radius analysis — returns every file that could be affected\n * if `file` changes, annotated with the package it belongs to. Requires a prior `analyze` call with no entry points.\n * @param cache - Session state holding the cached WorkspaceGraph.\n * @param args - `root` identifies the monorepo; `file` is the changed file (relative to root).\n * @returns TextResponse with `{ file, affected, count }` where each entry includes its package name.\n */\nexport async function handleGetWorkspaceAffected(\n cache: SessionState,\n args: GetWorkspaceAffectedArgs,\n): Promise<TextResponse> {\n const { root, file } = args;\n const wg = await cache.ensureFreshWorkspace(root);\n const affected = wg.getAffectedAcrossPackages(file);\n return text({ file, affected, count: affected.length });\n}\n\n/**\n * @description Drops the cached graph for `root` so the next `analyze` call rebuilds from disk.\n * Call this after editing source files mid-session to prevent stale query results. Config is preserved.\n * @param cache - Session state from which the cached graph will be removed.\n * @param args - `root` identifies which project's cache to invalidate.\n * @returns TextResponse with `{ root, cleared, message }` indicating whether a cache entry was present and removed.\n */\nexport function handleClearCache(cache: SessionState, args: ClearCacheArgs): TextResponse {\n const { root } = args;\n const cleared = cache.invalidate(root);\n return text({\n root,\n cleared,\n message: cleared\n ? \"Cache cleared. Call analyze to rebuild.\"\n : \"No cache was present for this root.\",\n });\n}\n\n/**\n * @description Returns type-level relationships for the project. Without `type`, returns an inventory\n * of all interfaces, classes, enums, and type aliases. With `type`, returns which files import that\n * type and which types the defining file itself imports. Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `type` is the exact exported name to look up (omit for full inventory).\n * @returns TextResponse with either a full type inventory or a focused `TypeQueryResult`.\n */\nexport async function handleGetTypeGraph(\n cache: SessionState,\n args: GetTypeGraphArgs,\n): Promise<TextResponse> {\n const { root, type } = args;\n const graph = await cache.ensureFresh(root);\n const typeGraph = buildTypeGraph(graph);\n if (type) {\n return text(queryTypeGraph(typeGraph, type));\n }\n const types = Array.from(typeGraph.types.values());\n return text({ count: types.length, types });\n}\n\n/**\n * @description Returns what each file is responsible for: its semantic role, JSDoc description,\n * exported symbol names, and which feature hub it belongs to. Pass `paths` to filter to specific\n * files, or omit to get all files. Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `paths` filters to specific files; `minOutDegree` tunes hub detection.\n * @returns TextResponse with `{ count, modules }` where each module includes its role, description, and exports.\n */\nexport async function handleGetModuleResponsibility(\n cache: SessionState,\n args: GetModuleResponsibilityArgs,\n): Promise<TextResponse> {\n const { root, paths, minOutDegree } = args;\n const graph = await cache.ensureFresh(root);\n const respGraph = buildResponsibilityGraph(\n graph,\n minOutDegree !== undefined ? { minOutDegree } : undefined,\n );\n if (paths?.length) {\n const modules = paths.map((modulePath) => respGraph.get(modulePath)).filter(Boolean);\n return text({ count: modules.length, modules });\n }\n const modules = Array.from(respGraph.values());\n return text({ count: modules.length, modules });\n}\n\n/**\n * @description Groups files into feature domains under their respective hub files (high-import\n * orchestrators). Each file is assigned to the most specific hub that can transitively reach it.\n * Returns up to 85–95% fewer tokens than a full graph query for domain-based questions.\n * Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `minOutDegree` sets the minimum internal imports to qualify as a hub.\n * @returns TextResponse with `{ features, unassigned }` where `features` is a plain object keyed by feature name.\n */\nexport async function handleGetFeatureGraph(\n cache: SessionState,\n args: GetFeatureGraphArgs,\n): Promise<TextResponse> {\n const { root, minOutDegree } = args;\n const graph = await cache.ensureFresh(root);\n const featureGraph = buildFeatureGraph(\n graph,\n minOutDegree !== undefined ? { minOutDegree } : undefined,\n );\n const features = Object.fromEntries(featureGraph.features);\n return text({ features, unassigned: featureGraph.unassigned });\n}\n\n/**\n * @description Looks up callers and callees for a named function. Returns the file that defines\n * the function, all files/functions that call it, and all files/functions it calls. Call edges\n * are only populated for TypeScript/JavaScript files. Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `function` is the exact name of the function to look up.\n * @returns TextResponse with `{ functionName, definedIn, callers, callees }`.\n */\nexport async function handleGetCallGraph(\n cache: SessionState,\n args: GetCallGraphArgs,\n): Promise<TextResponse> {\n const { root, function: functionName } = args;\n const graph = await cache.ensureFresh(root);\n return text(queryCallGraph(graph, functionName));\n}\n\n/**\n * @description Builds the API surface report for a project, expanding `export *` chains so every\n * symbol accessible to consumers is listed. Partitions the graph into `internalFiles`,\n * `unreachableFromEntry` (separate consumers or dead-code candidates), and `testFiles`. When `entryPoints` is omitted,\n * auto-detects them from `package.json` exports/main/module fields. Requires a prior `analyze` call.\n * @param cache - Session state holding the cached graph for `root`.\n * @param args - `root` selects the graph; `entryPoints` are the public entry files (auto-detected when omitted).\n * @returns TextResponse with `{ entryPoints, publicExports, internalFiles, unreachableFromEntry, testFiles }`.\n */\nexport async function handleGetApiSurface(\n cache: SessionState,\n args: GetApiSurfaceArgs,\n): Promise<TextResponse> {\n const { root, entryPoints } = args;\n const graph = await cache.ensureFresh(root);\n const eps = entryPoints?.length ? entryPoints : detectAllEntryPoints(graph, root);\n if (eps.length === 0) {\n throw new Error(\n \"No entry points found. Pass entryPoints explicitly or ensure package.json has a main/exports field.\",\n );\n }\n return text(buildApiSurface(graph, eps));\n}\n\n/**\n * @description Writes `@tag` annotations into every test file reachable from the cached graph.\n * Only `\"import\"` and `\"comment-marker\"` kind tags are written. Tags already present in the\n * file are excluded from the generated block to avoid duplication. The block is idempotent:\n * re-running replaces the existing block in place. Supports both TypeScript/JavaScript and\n * Gherkin `.feature` files with format-appropriate block syntax. Requires a prior `analyze` call.\n * @param {SessionState} cache - Session state holding the cached graph for `root`.\n * @param {ApplyTagsArgs} args - `root` selects the graph; `dryRun` previews changes without disk writes.\n * @returns {Promise<TextResponse>} TextResponse with `ApplyTagsResult`: aggregate counts and per-file status.\n */\nexport async function handleApplyTags(\n cache: SessionState,\n args: ApplyTagsArgs,\n): Promise<TextResponse> {\n const graph = await cache.ensureFresh(args.root);\n const result = await applyTags(graph, args.root, { dryRun: args.dryRun ?? false });\n return text(result);\n}\n","/** MCP response helpers: text() response wrapper and validateRoot() path guard. */\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TextResponse = { content: [{ type: \"text\"; text: string }] };\n\n/** Wraps any JSON-serializable value as an MCP text content response block. */\nexport function text(data: unknown): TextResponse {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\n/**\n * @description Validates that `root` is an absolute path within the user's home directory and points to an existing directory.\n * Called once per request before any tool handler runs, so handlers never receive an out-of-bounds root.\n * @param {string} root - The project root path supplied by the MCP caller.\n * @throws {Error} with a generic message if any constraint is violated — messages are intentionally vague to avoid leaking filesystem structure.\n */\nexport function validateRoot(root: string): void {\n if (!path.isAbsolute(root)) {\n throw new Error(\"root must be an absolute path\");\n }\n const resolved = path.resolve(root);\n const home = os.homedir();\n if (resolved !== home && !resolved.startsWith(home + path.sep)) {\n throw new Error(\"root must be within the user home directory\");\n }\n let stat: fs.Stats;\n try {\n stat = fs.statSync(resolved);\n } catch {\n throw new Error(\"root does not exist\");\n }\n if (!stat.isDirectory()) {\n throw new Error(\"root is not a directory\");\n }\n}\n","/**\n * MCP tool schema definitions for the mokosh server.\n *\n * Each entry is a complete JSON Schema description of one callable tool.\n * This list is returned verbatim by the `ListTools` request handler and\n * drives IDE/agent autocompletion for tool arguments.\n *\n * Tool call order requirement: `analyze` must be called first to populate the\n * in-session graph cache. All other tools except `find_unused` and `query`\n * require a prior `analyze` call for the same `root`.\n */\nexport const TOOL_DEFINITIONS = [\n {\n name: \"analyze\",\n description:\n \"Build the dependency graph for a project from entry points. Returns a summary of node count, categories, and cycles. Must be called before get_dependencies, get_dependents, get_affected, or propose_tags. Pass an empty entryPoints array to auto-detect a monorepo (pnpm/npm/yarn/Nx/Turborepo) and build per-package graphs — use get_workspace_packages and get_workspace_affected for monorepo queries.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: {\n type: \"string\",\n description: \"Absolute path to the project root (or monorepo root)\",\n },\n entryPoints: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Entry point files relative to root (e.g. ['src/index.ts']). Pass [] to trigger monorepo auto-detection.\",\n },\n },\n required: [\"root\", \"entryPoints\"],\n },\n },\n {\n name: \"get_dependencies\",\n description:\n \"Get files that a given file imports (outgoing traversal). depth=1 returns immediate imports; omit for the full transitive tree. Each result includes the specific symbols imported from that file (when known).\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n file: { type: \"string\", description: \"File path relative to root\" },\n depth: { type: \"number\", description: \"Max traversal depth (default: 1)\" },\n },\n required: [\"root\", \"file\"],\n },\n },\n {\n name: \"get_dependents\",\n description:\n \"Get files that directly import a given file (one-hop incoming edges). Each result includes the specific symbols that dependent file imports from this file (when known).\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n file: { type: \"string\", description: \"File path relative to root\" },\n },\n required: [\"root\", \"file\"],\n },\n },\n {\n name: \"get_affected\",\n description:\n \"Get all files transitively affected if a given file changes — full incoming traversal upward. Use before a refactor to understand blast radius. Set testsOnly=true to get only test files. Set cached=true to use a pre-computed O(1) lookup cache instead of graph traversal — faster on repeated calls for the same root. Pass changedSymbols to restrict blast-radius to files that actually import those symbols — omit to treat the whole file as changed.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n file: { type: \"string\", description: \"File path relative to root\" },\n testsOnly: {\n type: \"boolean\",\n description: \"Return only test/spec files (default: false)\",\n },\n cached: {\n type: \"boolean\",\n description:\n \"Use a pre-computed impact cache for O(1) lookup instead of graph traversal. Cache is built lazily on first use and reused for the session (default: false).\",\n },\n changedSymbols: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Restrict blast-radius to files that import at least one of these symbols. Omit to treat the whole file as changed (conservative, same as before).\",\n },\n },\n required: [\"root\", \"file\"],\n },\n },\n {\n name: \"get_callers\",\n description:\n \"Get files whose exported functions call into a given file (call-graph dependents). More precise than get_affected: only files with actual runtime call edges, not mere imports. Requires prior analyze() call.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to project root\" },\n file: { type: \"string\", description: \"File path relative to root\" },\n depth: { type: \"number\", description: \"Max traversal depth (default: 1)\" },\n withEdgeDetail: {\n type: \"boolean\",\n description: \"Include from/to function names per edge (default: false)\",\n },\n },\n required: [\"root\", \"file\"],\n },\n },\n {\n name: \"find_unused\",\n description:\n \"Find files in the project that are not reachable from any entry point. Useful before cleanup passes.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n entryPoints: { type: \"array\", items: { type: \"string\" } },\n },\n required: [\"root\", \"entryPoints\"],\n },\n },\n {\n name: \"find_uncovered\",\n description:\n \"Find non-test files whose line coverage is below the configured threshold. Requires a prior analyze() call and coverageReportPath set in mokosh.config. coverageThreshold overrides the config default (default: 80).\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n coverageThreshold: {\n type: \"number\",\n description:\n \"Line-coverage % below which a file is considered uncovered. Overrides config value.\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"find_complex_functions\",\n description:\n \"Find individual functions/methods above a cognitive (or cyclomatic) complexity threshold, sorted worst-first. Requires a prior analyze() call. TypeScript/JavaScript only.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n metric: {\n type: \"string\",\n enum: [\"cognitiveComplexity\", \"complexity\"],\n description: \"Which score to threshold/sort on (default: cognitiveComplexity)\",\n },\n threshold: {\n type: \"number\",\n description: \"Minimum score to include (default: 10)\",\n },\n limit: {\n type: \"number\",\n description: \"Max results to return, worst-first (default: 20)\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"propose_tags\",\n description:\n \"Propose what to run based on changed files. Pass changedFiles explicitly or omit to use git diff. format='tags' (default) returns test tags for CI tag-filtering; format='paths' returns test file paths ready to pipe directly to a test runner (e.g. vitest). Feature hubs act as traversal boundaries in both modes.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n changedFiles: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Changed files relative to root. Omit to read from git diff.\",\n },\n featureThreshold: {\n type: \"number\",\n description:\n \"Min importers for a file to be treated as a feature hub (default: 5). A hub short-circuits traversal and emits a feature:<name> tag instead of all downstream tags.\",\n },\n format: {\n type: \"string\",\n enum: [\"tags\", \"paths\"],\n description:\n \"Output format: 'tags' returns test tag names for CI filtering (default); 'paths' returns test file paths to pipe to a test runner.\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"detect_features\",\n description:\n \"Identify feature hub files — non-test files that import many other internal modules (orchestrators / aggregators like src/parser.ts or src/cli/runner.ts). Returns a list sorted by import count descending.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n entryPoints: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Entry point files relative to root. Omit to use the cached graph.\",\n },\n featureThreshold: {\n type: \"number\",\n description:\n \"Min internal imports a file must have to qualify as a feature hub (default: 5).\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"query\",\n description:\n \"Filter the graph by category, tag, or path. Returns matching nodes as JSON or a Mermaid diagram. If entryPoints is omitted the cached graph from a prior 'analyze' call is used.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\" },\n entryPoints: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Entry points to build the graph. Omit to reuse the cached graph from a prior 'analyze' call.\",\n },\n filter: {\n type: \"string\",\n description:\n \"Query string e.g. 'category:logic' or 'category:logic,tag:auth'. Supports: category, type, tag, path, external, importsFile, importedBy, minImports, maxImports, minSize, maxSize, hasDocstring, minCoverage, maxCoverage, minExportUsage, maxExportUsage. sort: size|imports|commitCount90d|exportUsage. limit: N.\",\n },\n mermaid: { type: \"boolean\", description: \"Return a Mermaid diagram (default: false)\" },\n slim: {\n type: \"boolean\",\n description:\n \"Compact response mode (default: true). Returns export names, meaningful tags, and a flat importsFiles path list — no edge objects, no mtime/size. Pass false only when full edge metadata is needed.\",\n },\n },\n required: [\"root\", \"filter\"],\n },\n },\n {\n name: \"get_workspace_packages\",\n description:\n \"List all workspace packages detected in a monorepo, with their node counts and inter-package dependencies. Requires a prior analyze() call with empty entryPoints on a monorepo root.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the monorepo root\" },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"get_workspace_affected\",\n description:\n \"Cross-package blast-radius analysis. Returns every file that could be affected if a given file changes, annotated with the package it belongs to. Requires a prior analyze() call with empty entryPoints on a monorepo root.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the monorepo root\" },\n file: {\n type: \"string\",\n description:\n \"Monorepo-root-relative path of the changed file (e.g. 'packages/shared/src/utils.ts')\",\n },\n },\n required: [\"root\", \"file\"],\n },\n },\n {\n name: \"get_type_graph\",\n description:\n \"Return type-level relationships for the project. Without a type name, returns an inventory of all interfaces, classes, enums, and type aliases with their file and kind. With a type name, returns which files import that type (usedByFiles) and which types the defining file imports (uses). Requires a prior analyze() call. Only covers TypeScript/JavaScript files.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n type: {\n type: \"string\",\n description:\n \"Exact exported name of the type to look up (e.g. 'FileNode'). Omit to get the full type inventory.\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"get_module_responsibility\",\n description:\n \"Return what each file is responsible for: its semantic role, JSDoc description (when present), exported symbol names, and which feature hub it belongs to. Pass specific paths to filter, or omit paths to get all files. Requires a prior analyze() call.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n paths: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Project-relative file paths to include. Omit to return all files.\",\n },\n minOutDegree: {\n type: \"number\",\n description: \"Min imports for a file to qualify as a feature hub (default: 5).\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"get_feature_graph\",\n description:\n \"Group files by domain: returns which files each feature hub (high-import orchestrator) transitively owns. Each file is assigned to the most specific hub that can reach it (lowest out-degree wins). Use this instead of a full query when answering 'what files are in the X feature/module?' — typically 85–95% fewer tokens than a full graph query.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n minOutDegree: {\n type: \"number\",\n description:\n \"Minimum internal imports a file must have to qualify as a feature hub (default: 5).\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"get_call_graph\",\n description:\n \"Look up callers and callees for a named function. Returns the file that defines the function, all files/functions that call it, and all files/functions it calls. Always requires a function name — never returns the full call graph unfiltered. Call edges are only populated for TypeScript/JavaScript files.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n function: {\n type: \"string\",\n description: \"Exact name of the function to look up (e.g. 'parseFile').\",\n },\n },\n required: [\"root\", \"function\"],\n },\n },\n {\n name: \"get_api_surface\",\n description:\n \"Build the API surface report for a project. Expands export* chains so every symbol accessible to consumers is listed (not just those directly declared in the entry file). Each export is resolved to its defining file and tagged with a kind (function/class/interface/type/enum/const). The graph is partitioned into: internalFiles (implementation reachable from entry points), unreachableFromEntry (non-test files not reachable from any entry point — may be separate consumers like CLI/MCP, config, or dead code), and testFiles (test suite). Supports multiple public entry points for libraries with sub-path exports. Requires a prior analyze() call. When entryPoints is omitted, auto-detects all entry points from package.json exports/main/module.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root\" },\n entryPoints: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Project-relative paths of public entry points (e.g. ['src/index.ts', 'src/utils.ts']). Omit to auto-detect from package.json exports / main / module fields.\",\n },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"clear_cache\",\n description:\n \"Drop the cached dependency graph for a project root, forcing the next analyze() call to rebuild from disk. Call this after editing source files mid-session — otherwise get_affected, get_dependencies, and other query tools will reason from stale data.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: { type: \"string\", description: \"Absolute path to the project root to invalidate.\" },\n },\n required: [\"root\"],\n },\n },\n {\n name: \"apply_tags\",\n description:\n \"Write @tag annotations into test file source code based on the dependency graph. Tags of kind 'import' (filename-derived) and 'comment-marker' (domain semantic, propagated from source files) are written as an idempotent block. Re-running is safe: the existing block is replaced in place. Tags already present in the file are excluded from the block to avoid duplication. Supports TypeScript/JavaScript (// <mokosh-tags> block with // @tag lines) and Gherkin .feature files (# <mokosh-tags> block with @tagname lines). Use dryRun=true to preview changes without writing to disk. Requires a prior analyze() call.\",\n inputSchema: {\n type: \"object\",\n properties: {\n root: {\n type: \"string\",\n description: \"Absolute path to the project root.\",\n },\n dryRun: {\n type: \"boolean\",\n description:\n \"When true, computes which files would change but does not write to disk (default: false).\",\n },\n },\n required: [\"root\"],\n },\n },\n] as const;\n"],"mappings":"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","names":["StdioServerTransport","Server","CallToolRequestSchema","ListToolsRequestSchema","fs","fs","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","__require","applyConfig","config","pattern","registerConfigMatcher","registerTestPattern","lib","registerTestLibrary","setBarrelThreshold","DEFAULT_IGNORE_DIRS","DEFAULT_EXTENSIONS","fs","path","loadCoverageMap","rootDir","reportPath","absoluteReport","raw","summary","map","absPath","entry","pct","relative","MermaidExporter","graph","lines","visitedEdges","node","nodeLabel","imp","targetLabel","edgeKey","edgeStyle","fs","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","fs","path","computeGraphHash","graph","entries","pathA","pathB","filePath","node","hash","i","buildChangeImpactCache","impact","affected","queryChangeImpact","cache","path","buildOutDegreeMap","nodes","outDegreeMap","filePath","node","count","imp","buildFeatureMap","minOutDegree","result","outDegree","ext","basename","label","detectFeatures","options","DEFAULT_HUB_COMPARATOR","left","right","collectReachable","graph","hubs","reachable","hub","files","node","assignFilesToHubs","nodes","comparator","fileToHub","filePath","bestHub","buildDomains","features","featureName","ownerHub","collectUnassigned","unassigned","buildFeatureGraph","options","detectFn","detectFeatures","GraphAnalyzer","nodes","allFiles","usedFiles","file","threshold","results","node","tightest","best","imp","left","right","cycles","visited","recStack","currentPath","find","current","cycleIndex","nodePath","Graph","_Graph","nodes","serialized","node","incoming","imp","list","startPath","visitor","options","getNeighbors","visited","maxDepth","walk","currentPath","depth","parentPath","neighbor","direction","path","importEdge","cache","edge","callIncoming","callEdge","filePath","callers","allFiles","GraphAnalyzer","inferRole","node","filePath","seg","fileBasename","segment","name","buildResponsibilityGraph","graph","featureOptions","featureGraph","buildFeatureGraph","fileToHub","featureName","domain","filePath","result","node","hub","inferRole","exportedSym","SymbolTraversalContext","startPath","affectedSymbols","visitedNode","childPath","currentSymbols","importEdge","imp","importedSymbols","relevantSymbols","sym","existing","symbol","inferKind","signature","isTypeExport","sym","category","sig","buildTypeGraph","graph","types","node","exp","key","edges","imp","queryTypeGraph","typeGraph","typeName","target","typeNode","usedByFiles","usesMap","edge","dep","path","fs","path","fs","path","fs","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","fs","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","fs","path","yaml","pnpmDetector","rootDir","yamlPath","path","fs","patterns","yaml","resolveGlobPatterns","fs","path","turborepoDetector","rootDir","fs","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","matchPath","matchIsExternal","importEdge","matchTags","positiveTags","tag","negativeTags","structuredTag","matchAllTags","matchImportsFile","matchImportedBy","reverseIndex","importerPath","matchMinImports","matchMaxImports","matchMinSize","matchMaxSize","matchHasDocstring","matchMinCoverage","matchMaxCoverage","matchMinExportUsage","matchMaxExportUsage","NODE_MATCHERS","matchNode","node","query","reverseIndex","NODE_MATCHERS","matcher","filterGraph","graph","imp","arr","filteredNodes","nodePaths","resultNodes","nodeA","nodeB","cycle","path","parseQuery","queryString","query","parts","part","colonIdx","key","value","fs","path","path","ts","path","ts","ts","ANNOTATABLE_NAMES","findTopLevelCalls","sourceFile","calls","stmt","expr","callee","readArrayProp","call","propName","sf","arg","prop","candidate","element","buildInjectReplacement","tagsLiteral","i","existingProp","closeBrace","callback","buildRemoveReplacement","args","idx","applyReplacements","source","replacements","sorted","left","right","result","replacement","toArrayLiteral","tags","tag","TS_EXTENSIONS","toCypressLiteral","tags","toArrayLiteral","tag","normaliseExisting","raw","CypressStrategy","absPath","TS_EXTENSIONS","path","source","sf","ts","calls","findTopLevelCalls","rawExisting","readArrayProp","sortedTags","replacements","call","replacement","buildRemoveReplacement","buildInjectReplacement","applyReplacements","path","BLOCK_REGEX","EXISTING_TAG_REGEX","buildBlock","tags","tag","readManualTags","content","found","match","GherkinStrategy","absPath","_absPath","source","manualContent","manualTags","netNewTags","newBlock","matchesGlob","pattern","relPath","normalizedPattern","normalizedPath","regexSource","i","char","path","MOKOSH_BUILD_TAG_RE","PACKAGE_LINE_RE","buildBuildTag","tags","tag","readExistingTags","source","match","line","re","tagMatch","GoStrategy","absPath","_absPath","existing","sortedTags","buildTag","packageMatch","insertAt","path","GROUP_BLOCK_RE","GROUP_LINE_RE","buildBlock","tags","tag","readExistingGroups","block","found","match","JestStrategy","absPath","TS_EXTENSIONS","path","_absPath","source","existing","sortedTags","stripped","path","ts","toPlaywrightLiteral","tags","toArrayLiteral","tag","normaliseExisting","raw","PlaywrightStrategy","absPath","TS_EXTENSIONS","path","source","sf","ts","calls","findTopLevelCalls","rawExisting","readArrayProp","sortedTags","replacements","call","replacement","buildRemoveReplacement","buildInjectReplacement","applyReplacements","path","PYTESTMARK_RE","PYTEST_IMPORT_RE","buildPytestmark","tags","marks","tag","readExistingMarks","source","match","line","re","markMatch","PytestStrategy","absPath","_absPath","existing","sortedTags","pytestmarkLine","hasImport","importBlockEnd","findImportBlockEnd","before","after","importLine","separator","lines","lastImportLine","i","offset","path","ts","LEGACY_BLOCK_REGEX","VitestStrategy","absPath","TS_EXTENSIONS","path","source","tags","stripped","sf","ts","calls","findTopLevelCalls","existing","readArrayProp","sortedTags","replacements","call","r","buildRemoveReplacement","buildInjectReplacement","toArrayLiteral","applyReplacements","FRAMEWORK_STRATEGIES","VitestStrategy","PlaywrightStrategy","CypressStrategy","JestStrategy","FRAMEWORK_IMPORT_MARKERS","detectFrameworkFromImports","source","sf","ts","stmt","framework","AutoFrameworkStrategy","rootDir","defaultFramework","frameworkOverrides","absPath","TS_EXTENSIONS","path","tags","relPath","pattern","matchesGlob","createStrategies","GherkinStrategy","PytestStrategy","GoStrategy","getStrategyForFile","strategies","strategy","VALID_TAG_NAME_RE","ALLOWED_TAG_KINDS","GENERIC_TAG_BLOCKLIST","applyTagsToFile","absPath","tags","dryRun","strategies","original","fs","err","strategy","getStrategyForFile","newContent","applyTags","graph","rootDir","options","config","loadMokoshConfig","framework","frameworkOverrides","createStrategies","result","node","seen","tagNames","tag","path","fileResult","DefaultTestNodeIdentifier","node","tag","fs","path","execSync","DefaultGitProvider","allFiles","cmd","filePath","error","getGitFileStats","rootDir","relativePath","lines","fs","path","yaml","stripVersionSuffix","descriptor","lastAt","parseYarnDescriptors","line","part","trimmed","parsePnpmId","id","pkgVersion","raw","tryParseYarnBerry","content","lock","result","key","value","name","parseYarnClassic","block","lines","header","names","version","parsePackageLock","filePath","pkgPath","pkgData","parseYarnLock","berryResult","parsePnpmLock","versionData","loadLockFile","rootDir","candidates","filename","parser","path","getFileType","filePath","isStyleFile","specifier","ext","coffee","extractTags","content","tags","tagRegex","match","resolveCategory","filePath","lower","edgeFromImportDeclaration","node","specifier","isStyleFile","edgeFromRequireCall","isRequire","visitNode","out","className","edge","traverse","key","child","c","parseCoffeeScript","category","imports","coffee","name","AstBuilder","GherkinClassicTokenMatcher","Parser","IdGenerator","uuidFn","IdGenerator","parseGherkin","_filePath","content","rawTags","builder","AstBuilder","matcher","GherkinClassicTokenMatcher","gherkinDocument","Parser","tag","child","example","ruleChild","error","name","registerParser","path","parser","TAG_RE","BUILD_NEW_RE","BUILD_OLD_RE","parseGo","filePath","content","imports","exportMap","tags","buildTags","cursor","text","tagM","newBuild","extractBuildTokens","oldBuild","stringNode","specifier","nameNode","name","importsTestingPkg","importEdge","category","allTagNames","expr","out","tok","ls","stripQuotes","value","extractTags","content","tags","tagRegex","match","classifyFile","filePath","lower","POSITIONAL_KEYS","extractEdge","node","type","raw","specifier","stripQuotes","isStyleFile","call","collectEdges","edges","edge","key","child","c","parseLiveScript","category","imports","ls","name","luaparse","extractTagAnnotations","content","tagNames","tagAnnotationRegex","annotationMatch","classifyCategory","filePath","lowerCasePath","collectRequireEdges","ast","importEdges","visitNode","node","specifier","requireArgument","stripQuotes","isStyleFile","key","childValue","childNode","parseLua","category","imports","luaparse","name","path","parser","TEST_LIBS","parsePython","filePath","content","imports","exports","tags","baseName","cursor","tagMatch","edge","extractImportEdges","parentNode","nameNode","target","category","resolveCategory","name","node","src","first","extractFromImport","extractBareImport","fromKw","importKw","rawModule","importedNames","collectImportedNames","dotCount","modulePart","makeEdge","prefix","edges","childNode","modName","start","names","rawSpecifier","symbols","isExternal","imp","path","ts","ts","computeCyclomaticComplexity","rootNode","complexity","walkCyclomatic","node","operatorKind","computeCognitiveComplexity","cognitiveComplexity","walkCognitive","depth","isElseIf","bodyDepth","child","computeComplexity","ts","TEST_CALL_NAMES","handleTagging","node","ctx","collectDeclarationNameTags","collectStringLiteralAtTags","collectCommentAnnotationTags","collectVitestOptionBagTags","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","postcss","lessParser","SIDE_EFFECT_KEYWORDS","isExternalCss","specifier","isLocalUrl","trimmed","extractAtImportEdge","params","filePath","lessMatch","keyword","type","urlMatch","extractUrlDeclarationEdges","value","edges","urlPattern","match","collectEdgesFromRoot","root","imports","node","edge","stripLineComments","content","regexFallbackImports","atImportPattern","parseCssContent","parseLessContent","scssParse","isScssExternal","specifier","parseScssParams","params","specMatch","alias","parseScssContent","content","filePath","root","imports","node","name","edge","parseStylusImports","content","filePath","imports","atRequirePattern","match","specifier","bareImportPattern","detectStylusCategory","stylusLib","astNode","parseStyleFile","filePath","content","fileType","getFileType","imports","parseStylusImports","detectStylusCategory","root","parseScssContent","detectCssBarrel","parseLessContent","parseCssContent","type","parser","path","content","parseCodeFile","parseStyleFile","parseCoffeeScript","parseLiveScript","parseLua","parsePython","parseGo","parseGherkin","registerParser","parseFile","filePath","fileType","getFileType","getParserForType","path","enrichCoverage","nodes","coverageMap","node","pct","enrichLibraryTags","imports","tags","imp","libName","existingTag","enrichTestedBy","target","round4","value","enrichExportUsage","ratios","ratio","sum","addUniqueTag","name","kind","addFilenameTag","testNode","importEdge","toPath","filenameTag","addSymbolTags","symbolName","propagateCommentMarkers","sourceNode","sourceTag","enrichTestNodeTags","fs","path","fs","path","GoLangResolver","_currentFile","specifier","rootDir","_resolveLocal","mod","replaces","redirected","goFilesInDir","rel","cached","empty","content","data","parseGoMod","from","toDir","sub","lines","inReplaceBlock","raw","line","parseReplaceLine","out","lhs","rhs","side","fromModule","absTarget","absDir","entries","files","dirent","resolvedA","resolvedB","fs","path","LuaLangResolver","_currentFile","specifier","rootDir","resolveLocal","luaSpecifier","searchBases","base","resolved","fs","path","PythonLangResolver","_currentFile","specifier","rootDir","_resolveLocal","pyPath","pyFile","isFile","initFile","filePath","DefaultResolver","rootDir","options","PythonLangResolver","LuaLangResolver","GoLangResolver","currentFile","specifier","aliased","resolved","resolveLocal","cf","spec","lr","ext","locals","workspace","dir","path","fullPath","isExternal","extensions","esmMatch","strippedPath","candidatePath","indexP","initP","filePath","fs","searchDir","tsconfigPath","paths","alias","match","regex","pattern","substitutions","wildcardMatch","baseDir","sub","resolvedSub","pkgName","pkgRoot","subPath","base","candidate","abs","deepResolved","CONVENTIONAL_TEST_DIR_NAMES","commonAncestorDir","absPaths","rootDir","segmentLists","p","path","common","segments","i","candidate","rel","GraphBuilder","previousGraph","resolver","progressCallback","enableGitStats","coverageMap","DefaultResolver","loadLockFile","entryPoints","entryPaths","entry","entryPath","enrichTestNodeTags","enrichTestedBy","enrichExportUsage","enrichCoverage","Graph","scanRoot","patterns","getTestPatterns","ignoreDirs","walk","dir","entries","fs","fullPath","pattern","parent","name","filePath","stats","relativePath","node","cachedNode","parsed","enrichLibraryTags","callEdges","content","parseFile","err","getFileType","rawCallEdges","rce","resolved","imports","exports","tags","category","description","complexity","cognitiveComplexity","functions","git","getGitFileStats","resolvedImports","imp","results","edge","libName","dep","resolveOptions","graph","options","DefaultTestNodeIdentifier","detectFeatures","traverseAffected","changedFiles","featureMap","onFeatureHub","onNode","changed","startNode","context","SymbolTraversalContext","exportedSym","visitedNode","depth","childPath","feature","proposeTags","identifier","proposedTags","node","tag","proposeAffectedTests","affectedTests","fs","path","createImportMap","rootDir","entryPoints","previousGraph","options","progressCallback","count","GraphBuilder","path","createWorkspaceGraph","abs","layout","detectMonorepo","pkgs","pkg","workspaceMap","wg","WorkspaceGraph","graph","DefaultResolver","getAllProjectFiles","files","ignoreDirs","DEFAULT_IGNORE_DIRS","extensions","DEFAULT_EXTENSIONS","walk","dir","entries","fs","entry","fullPath","IGNORE_WATCH","SessionState","root","config","entryPoints","coverageMap","graph","createImportMap","options","cached","wg","createWorkspaceGraph","existing","cache","buildChangeImpactCache","args","watcher","fs","_event","filename","had","path","fs","os","path","text","data","validateRoot","root","resolved","home","stat","handleAnalyze","cache","args","root","entryPoints","config","loadMokoshConfig","applyConfig","layout","detectMonorepo","wg","perPackage","graph","pkg","text","resolvedEntries","ep","path","coverageMap","loadCoverageMap","serialized","categories","acc","node","cycles","handleGetDependencies","file","depth","deps","_depth","parentPath","edge","importEdge","handleGetDependents","dependents","handleGetAffected","testsOnly","cached","changedSymbols","impactCache","allAffected","queryChangeImpact","affected","filePath","ctx","SymbolTraversalContext","isTest","tag","handleGetCallers","withEdgeDetail","callers","entry","callEdge","handleFindUnused","allFiles","getAllProjectFiles","unusedFiles","handleFindUncovered","coverageThreshold","threshold","uncovered","handleFindComplexFunctions","metric","limit","functions","fn","a","b","handleProposeTags","changedFiles","featureThreshold","format","files","DefaultGitProvider","opts","affectedTests","proposeAffectedTests","tags","proposeTags","handleDetectFeatures","featureMap","detectFeatures","features","featureA","featureB","handleQuery","filter","mermaid","slim","filtered","filterGraph","parseQuery","MermaidExporter","Graph","slimNodes","exportedSym","imp","handleGetWorkspacePackages","pkgDeps","packages","handleGetWorkspaceAffected","handleClearCache","cleared","handleGetTypeGraph","type","typeGraph","buildTypeGraph","queryTypeGraph","types","handleGetModuleResponsibility","paths","minOutDegree","respGraph","buildResponsibilityGraph","modules","modulePath","handleGetFeatureGraph","featureGraph","buildFeatureGraph","handleGetCallGraph","functionName","queryCallGraph","handleGetApiSurface","eps","detectAllEntryPoints","buildApiSurface","handleApplyTags","result","applyTags","TOOL_DEFINITIONS","createMcpServer","cache","SessionState","server","Server","ListToolsRequestSchema","TOOL_DEFINITIONS","CallToolRequestSchema","request","name","rawArgs","toolArgs","handler","args","handleAnalyze","handleGetDependencies","handleGetDependents","handleGetAffected","handleGetCallers","handleFindUnused","handleFindUncovered","handleFindComplexFunctions","handleProposeTags","handleDetectFeatures","handleGetTypeGraph","handleGetModuleResponsibility","handleGetFeatureGraph","handleGetCallGraph","handleGetApiSurface","handleQuery","handleGetWorkspacePackages","handleGetWorkspaceAffected","handleClearCache","handleApplyTags","validateRoot","err","main","server","createMcpServer","transport","StdioServerTransport"]}
|