@documentation-robotics/cli 0.3.0 → 0.4.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/README.md +10 -14
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- package/dist/analyzers/cards/build.js +21 -1
- package/dist/analyzers/cards/build.js.map +2 -2
- package/dist/analyzers/cards/extract.js +138 -0
- package/dist/analyzers/cards/extract.js.map +7 -0
- package/dist/analyzers/cards/routes.js +96 -0
- package/dist/analyzers/cards/routes.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +76 -4
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +12 -6
- package/dist/analyzers/cbm-responses.js.map +2 -2
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +6 -2
- package/dist/analyzers/edge-relationships.js.map +2 -2
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.js.map +7 -0
- package/dist/cli.js +12 -10
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +120 -18
- package/dist/coding-agents/drbot-mcp.js.map +2 -2
- package/dist/commands/add.js +4 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +35 -35
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +16 -8
- package/dist/commands/changeset-verify.js.map +2 -2
- package/dist/commands/changeset.js +41 -11
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +35 -31
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +2 -12
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +14 -13
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +28 -120
- package/dist/commands/upgrade.js.map +2 -2
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +30 -12
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +2 -1
- package/dist/core/catalog-migration.js.map +2 -2
- package/dist/core/changeset-base-index.js +7 -0
- package/dist/core/changeset-base-index.js.map +2 -2
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.js.map +7 -0
- package/dist/core/changeset-verify.js +356 -26
- package/dist/core/changeset-verify.js.map +3 -3
- package/dist/core/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/migration-registry.js +13 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- package/dist/core/relationship-staging.js +94 -1
- package/dist/core/relationship-staging.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +83 -3
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staging-area.js +85 -8
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- package/dist/generated/relationship-index.js +124 -15
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
- package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +7 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
- package/dist/integrations/claude_code/docs/README.md +20 -1
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
- package/dist/mcp/changeset-only.js +14 -3
- package/dist/mcp/changeset-only.js.map +2 -2
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +39 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
- package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +133 -57
- package/dist/mcp/tools/changeset-editing.js.map +3 -3
- package/dist/mcp/tools/changeset-lifecycle.js +1 -1
- package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +12 -6
- package/dist/mcp/tools/changeset-verify.js.map +2 -2
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +15 -0
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +189 -73
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-update.js +16 -12
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +89 -20
- package/dist/mcp/tools/relationship-tools.js.map +2 -2
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +10 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +22 -10
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1 -1
- package/dist/schemas/bundled/apm.json +1 -1
- package/dist/schemas/bundled/application.json +1 -1
- package/dist/schemas/bundled/base.json +1 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +1 -1
- package/dist/schemas/bundled/data-store.json +1 -1
- package/dist/schemas/bundled/manifest.json +3 -3
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +1 -1
- package/dist/schemas/bundled/product.json +1 -1
- package/dist/schemas/bundled/security.json +1 -1
- package/dist/schemas/bundled/technology.json +1 -1
- package/dist/schemas/bundled/testing.json +145 -1
- package/dist/schemas/bundled/ux.json +1 -1
- package/dist/server/server.js +508 -237
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/source-reference.js +58 -27
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +1 -1
- package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
- package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/cbm-analyzer.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * CBM (Codebase Memory) Analyzer Backend\n *\n * Implements the AnalyzerBackend interface for the codebase-memory-mcp analyzer.\n * Handles detection, indexing, and querying of code-based metadata using the CBM MCP server.\n *\n * Key responsibilities:\n * - Detect CBM binary availability and MCP registration\n * - Manage project indexing with freshness tracking\n * - Query for endpoints and other code artifacts\n * - Transform CBM nodes to DR elements using mapping templates\n */\n\nimport { spawnSync } from \"child_process\";\nimport * as path from \"path\";\nimport { readFile } from \"fs/promises\";\nimport { CLIError, ErrorCategory, handleWarning } from \"../utils/errors.js\";\nimport type { AnalyzerBackend, AnalyzerCodebaseOptions } from \"./base-analyzer.js\";\nimport type {\n AnalyzerStatus,\n DetectionResult,\n EndpointCandidate,\n IndexResult,\n IndexMeta,\n AnalyzerNodeMapping,\n AnalyzerHeuristic,\n HttpMethod,\n ServiceCandidate,\n DatastoreCandidate,\n CallGraphNode,\n VerifyOptions,\n VerifyReport,\n} from \"./types.js\";\nimport { VALID_HTTP_METHODS } from \"./types.js\";\nimport { StdioClient } from \"./stdio-client.js\";\nimport { readIndexMeta, writeIndexMeta } from \"./session-state.js\";\nimport type { MappingLoader } from \"./mapping-loader.js\";\nimport { getCliVersion } from \"./version.js\";\nimport { StagingAreaManager } from \"../core/staging-area.js\";\nimport { VerifyEngine } from \"./verify-engine.js\";\nimport { clampDepth, shapeCallGraphNode, determineEdgeType } from \"./call-graph-utils.js\";\nimport { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from \"./heuristic-utils.js\";\nimport { toKebabCase } from \"../utils/id-generator.js\";\nimport {\n CbmTools,\n CbmToolError,\n ROUTE_HANDLERS_QUERY,\n attachRouteHandlers,\n nodeColumns,\n nodesFromQuery,\n relativeToRoot,\n routeHandlersFromQuery,\n} from \"./cbm-responses.js\";\nimport type { CbmGraphNode } from \"./cbm-responses.js\";\nimport { cbmCardGraph } from \"./cards/graph.js\";\nimport { classifyTier0, defaultLabels, type ClassifyOptions, type ClassifyReport } from \"./cards/classify.js\";\nimport { CARD_FIELDS } from \"./cards/build.js\";\nimport { tier0RulesFrom } from \"./tier0-rules.js\";\n\n// Source-code file extensions that CBM sometimes mislabels as Route nodes when it\n// encounters string literals that look like paths (e.g. \"/src/app.ts\").\n// Only source-code extensions are listed \u2014 .json, .xml, .csv, .html etc. are\n// legitimate HTTP route suffixes and must NOT be filtered out.\nexport const SOURCE_CODE_EXT_RE = /\\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|java|rs|php|cs|cpp|c|h|kt|swift|sh|bash)$/i;\n\n/**\n * Returns true when a qualified_name looks like a real HTTP route rather than\n * a source-file path that the CBM indexer mislabelled as a Route node.\n */\nfunction isLikelyHttpRoute(qualifiedName: string): boolean {\n const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, \"\");\n // Reject source-code file paths, internal DR paths, and spec artifact paths\n // (e.g. /schemas/motivation/goal from test fixtures or openapi schema refs)\n return !SOURCE_CODE_EXT_RE.test(httpPath)\n && !httpPath.startsWith(\"/.dr/\")\n && !httpPath.startsWith(\"/schemas/\");\n}\n\n/**\n * Infer HTTP method from a function name using naming conventions.\n * Returns \"GET\" by default for ambiguous names.\n */\nfunction inferHttpMethodFromFunctionName(name: string): HttpMethod {\n const lower = name.toLowerCase();\n if (/^(create|add|post|submit|send)/.test(lower)) return \"POST\";\n if (/^(update|put|save|replace|set)/.test(lower)) return \"PUT\";\n if (/^(patch|merge)/.test(lower)) return \"PATCH\";\n if (/^(delete|remove|destroy)/.test(lower)) return \"DELETE\";\n return \"GET\";\n}\n\n/**\n * Infer an HTTP path from a function name by stripping the verb prefix and\n * converting the remainder to kebab-case.\n */\nfunction inferHttpPathFromFunctionName(name: string): string {\n const stripped = name.replace(/^(fetch|get|list|create|add|update|put|delete|remove|patch|merge|load|save|replace|post|submit|send|destroy)/i, \"\");\n const segment = toKebabCase(stripped || name);\n return segment ? `/${segment}` : \"/\";\n}\n\n/** Element types whose CBM labels can yield service candidates in services(). */\nconst SERVICE_SOURCE_TYPES = new Set([\"applicationfunction\", \"applicationcomponent\", \"applicationservice\"]);\n\n/**\n * CBM Analyzer implementation\n */\nexport class CbmAnalyzer implements AnalyzerBackend {\n private mapper: MappingLoader;\n private testCodePatternWarned = false;\n private warnedHeuristics = new Set<string>();\n\n /**\n * Test code detection when the mapping has no test_code_exclusion rule: the same test-file\n * pattern as the mapping's rule and its tier0_test_file rule.\n */\n private static readonly DEFAULT_TEST_PATTERNS = [\n /((^|\\/)(tests?|__tests__|__fixtures__|fixtures?)\\/|(^|\\/)test_[^/]*\\.py$|(^|\\/)conftest\\.py$|_test\\.(go|py)$|\\.(test|spec)\\.[cm]?[jt]sx?$|Tests?\\.(java|kt)$)/,\n ];\n\n constructor(mapper: MappingLoader) {\n this.mapper = mapper;\n }\n\n /**\n * Get the analyzer name\n */\n get name(): string {\n return this.mapper.getAnalyzerName();\n }\n\n /**\n * Get the analyzer display name\n */\n get displayName(): string {\n const metadata = this.mapper.getAnalyzerMetadata();\n return metadata?.display_name || this.mapper.getAnalyzerName();\n }\n\n /**\n * Check if the MCP server is registered in .mcp.json\n *\n * Reads .mcp.json from the project root and checks if the analyzer's MCP server\n * is registered there. This is extracted into a separate method for easier testing.\n *\n * @param projectRoot Absolute path to the project root\n * @returns True if the MCP server is registered, false otherwise\n * @throws CLIError if .mcp.json exists but is unreadable or malformed\n */\n async checkMcpRegistration(projectRoot: string): Promise<boolean> {\n try {\n const mcpJsonPath = path.join(projectRoot, \".mcp.json\");\n const mcpContent = await readFile(mcpJsonPath, \"utf-8\");\n const mcpConfig = JSON.parse(mcpContent);\n const metadata = this.mapper.getAnalyzerMetadata();\n const mcpServerName = metadata?.mcp_server_name ?? \"codebase-memory-mcp\";\n // Check if the analyzer's MCP server is registered in .mcp.json\n return (mcpConfig.mcpServers && mcpServerName in mcpConfig.mcpServers) || false;\n } catch (error) {\n // ENOENT (file not found) is expected - MCP registration is optional\n const err = error as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\") {\n return false;\n }\n\n // Other errors indicate actionable problems\n if (err.code === \"EACCES\") {\n throw new CLIError(\n \"Permission denied reading .mcp.json\",\n ErrorCategory.SYSTEM,\n [\n `Cannot read ${path.join(projectRoot, \".mcp.json\")}`,\n \"Check file permissions\",\n ]\n );\n }\n\n // SyntaxError during JSON.parse or other read/parse errors\n throw new CLIError(\n \"Invalid .mcp.json format\",\n ErrorCategory.VALIDATION,\n [\n `Check that ${path.join(projectRoot, \".mcp.json\")} contains valid JSON`,\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n ]\n );\n }\n }\n\n /**\n * Detect if the analyzer is installed and functional\n *\n * Checks for the presence of the analyzer binary, reads .mcp.json for registration info,\n * and validates the required tool contract by calling initialize and verifying required_tools.\n *\n * @param projectRoot Optional absolute path to project root (defaults to cwd)\n * @returns Detection result with binary path, version, and MCP registration status\n */\n async detect(projectRoot?: string): Promise<DetectionResult> {\n // Get binary names from the analyzer metadata\n const metadata = this.mapper.getAnalyzerMetadata();\n const binaryNames = (metadata?.binary_names as string[] | undefined) ?? [\n \"codebase-memory-mcp\",\n ];\n\n // Check if .mcp.json exists at project root for registration status\n // Use provided projectRoot or fallback to cwd\n let mcpRegistered = false;\n try {\n mcpRegistered = await this.checkMcpRegistration(projectRoot || process.cwd());\n } catch (error) {\n // CLIError from checkMcpRegistration indicates actionable problems (EACCES, syntax errors)\n // Surface these to the user via handleWarning(), but continue with detection\n if (error instanceof CLIError) {\n handleWarning(error.message, error.suggestions);\n } else {\n // Unexpected error - still continue with detection but warn\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Unexpected error checking MCP registration\", [errorMsg]);\n }\n mcpRegistered = false;\n }\n\n for (const binaryName of binaryNames) {\n // Check if binary is available using 'which'\n const whichResult = spawnSync(\"which\", [binaryName], {\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (whichResult.status !== 0) {\n continue;\n }\n\n const binaryPath = whichResult.stdout.trim();\n\n if (!binaryPath) {\n continue;\n }\n\n // Try to initialize the analyzer to validate the tool contract\n const client = new StdioClient();\n try {\n client.spawn(binaryPath);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Verify required_tools from mapping are available on the server\n let contractOk = true;\n const requiredTools = (metadata?.supported_tool_contract as Record<string, unknown> | undefined)?.required_tools as string[] | undefined ?? [];\n\n if (requiredTools.length > 0) {\n try {\n // Verify all required tools are listed in supported tools\n // The MCP spec requires tools to be registered at initialization\n const response = await client.callTool(\"tools/list\", {}) as { tools?: Array<{ name: string }> };\n\n const availableTools = new Set(\n (response.tools ?? []).map((tool) => tool.name)\n );\n\n for (const tool of requiredTools) {\n if (!availableTools.has(tool)) {\n contractOk = false;\n handleWarning(\n `Required tool '${tool}' not available in ${binaryName}`,\n [\"Ensure the analyzer server exports all required tools\"]\n );\n }\n }\n } catch (error) {\n // If tool listing fails, treat as verification failure\n contractOk = false;\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to verify required tools for ${binaryName}: ${errorMsg}`,\n [\"Ensure all required tools are available in the analyzer server\"]\n );\n }\n }\n\n // Successfully initialized and verified contract\n return {\n installed: true,\n binary_path: binaryPath,\n version: undefined, // Version could be extracted from --version if needed\n mcp_registered: mcpRegistered,\n contract_ok: contractOk,\n };\n } catch (error) {\n // Initialize failed - log diagnostic info and continue to next binary\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n handleWarning(\n `Binary ${binaryName} failed initialization: ${errorMessage}`,\n [\"Check that the binary is properly installed and functional\"]\n );\n continue;\n } finally {\n // Always close the client to prevent orphan processes\n try {\n client.close();\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to close MCP client for ${binaryName}: ${errorMessage}`,\n [\n \"The MCP server process may still be running\",\n \"Try restarting your terminal or manually killing the process\",\n ]\n );\n }\n }\n }\n\n // No binary found or functional\n return {\n installed: false,\n };\n }\n\n /**\n * Get the current status of the analyzer for a project\n *\n * Checks if the project is indexed and whether the index is fresh relative to git HEAD.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Current status including detection, index state, and freshness\n */\n async status(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<AnalyzerStatus> {\n const detected = await this.detect(projectRoot);\n\n // Resolve the effective codebase root for git operations\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Read index metadata if it exists (keyed to model root)\n const analyzerName = this.mapper.getAnalyzerName();\n const indexMeta = await readIndexMeta(projectRoot, analyzerName);\n\n // Check if indexed\n const indexed = indexMeta !== null;\n\n if (!indexed) {\n return {\n detected,\n indexed: false,\n fresh: false,\n };\n }\n\n // Check freshness: compare stored git HEAD to current HEAD\n // Run git in the effective codebase root, not the model root\n let fresh = false;\n try {\n const currentHeadResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: effectiveCodebaseRoot,\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (currentHeadResult.status === 0) {\n const currentHead = currentHeadResult.stdout.trim();\n fresh = currentHead === indexMeta.git_head;\n } else {\n // Git command failed - log diagnostic info but don't fail if codebaseRoot was provided\n const gitError = currentHeadResult.stderr?.trim() || \"git returned non-zero status\";\n if (options?.codebaseRoot) {\n handleWarning(\"Failed to check git freshness in codebase root\", [\n `Codebase root: ${effectiveCodebaseRoot}`,\n `Git error: ${gitError}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n } else {\n handleWarning(\"Failed to check git freshness\", [\n `Git error: ${gitError}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n }\n fresh = false;\n }\n } catch (error) {\n // Git spawn failed completely - log diagnostic info\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Failed to spawn git process\", [\n `Error: ${errorMsg}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n fresh = false;\n }\n\n return {\n detected,\n indexed: true,\n index_meta: indexMeta,\n fresh,\n last_indexed: indexMeta.timestamp,\n };\n }\n\n /**\n * Index a project or update the index\n *\n * Creates a project entry in the CBM backend if it doesn't already exist,\n * indexes the repository, and writes freshness metadata.\n *\n * Skips indexing if the index is fresh (git HEAD matches) unless --force is set.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional configuration including force flag and codebase root\n * @returns Index result with counts and metadata\n */\n async index(\n projectRoot: string,\n options?: { force?: boolean } & AnalyzerCodebaseOptions\n ): Promise<IndexResult> {\n // Resolve the effective codebase root for git and indexing operations\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Get current status (passing codebaseRoot option)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n\n // Skip if fresh and not forced\n if (status.indexed && status.fresh && !options?.force) {\n handleWarning(\"Index is fresh. Use --force to reindex.\", [\n `Last indexed: ${status.last_indexed}`,\n ]);\n\n // Return the existing metadata\n const meta = status.index_meta;\n return {\n node_count: meta.node_count,\n edge_count: meta.edge_count,\n git_head: meta.git_head,\n timestamp: meta.timestamp,\n };\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\n \"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\",\n \"Ensure the binary is in your PATH\",\n ]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Check if project is already indexed using list_projects\n // This validates the acceptance criterion and prevents duplicate project creation\n const projects = await tools.listProjects();\n const projectExists = projects.some((p) => p.root_path === effectiveCodebaseRoot);\n\n // Prevent duplicate CBM entries if project exists and index is fresh.\n // Per FR-3.3, --force is only needed to override a fresh index, not for stale re-indexing.\n // list_projects check prevents duplicate entries; freshness is already checked at line 313.\n if (projectExists && status.fresh && !options?.force) {\n handleWarning(\n \"Project already indexed in CBM backend with fresh index.\",\n [\"Use --force to re-index the project\"]\n );\n\n // Return actual index metadata instead of fabricated zeros\n // Guard against index_meta being undefined (can happen if metadata is corrupted or deleted)\n if (!status.index_meta) {\n throw new CLIError(\n \"Project exists in CBM backend but local metadata is missing\",\n ErrorCategory.VALIDATION,\n [\n \"Use --force to re-index and regenerate metadata\",\n \"Or manually delete the project from CBM backend and re-index\"\n ]\n );\n }\n\n const meta = status.index_meta;\n return {\n node_count: meta.node_count,\n edge_count: meta.edge_count,\n git_head: meta.git_head,\n timestamp: meta.timestamp,\n };\n }\n\n // Get current git HEAD from the effective codebase root\n // This must happen before index_repository to avoid inconsistent state\n const headResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: effectiveCodebaseRoot,\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (headResult.status !== 0) {\n throw new CLIError(\n \"Failed to get git HEAD\",\n ErrorCategory.SYSTEM,\n [\n \"Ensure the codebase root is a git repository\",\n `Codebase root: ${effectiveCodebaseRoot}`,\n \"Verify git is installed and functional\",\n `Error: ${headResult.stderr?.trim() || \"unknown error\"}`,\n ]\n );\n }\n\n const gitHead = headResult.stdout.trim();\n\n // Read and log the active changeset for consistency tracking (v1: logging only, no branching)\n try {\n const stagingManager = new StagingAreaManager(projectRoot);\n const activeChangesetId = await stagingManager.getActiveId();\n if (activeChangesetId) {\n console.log(`Indexing with active changeset: ${activeChangesetId}`);\n }\n } catch (error) {\n // If reading active changeset fails, log a warning but continue indexing\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Failed to read active changeset\",\n [`Error: ${errorMsg}`, \"Continuing with indexing\"]\n );\n }\n\n // Index the repository using the effective codebase root\n const indexResponse = await tools.indexRepository(effectiveCodebaseRoot);\n\n const timestamp = new Date().toISOString();\n const nodeCount = typeof indexResponse.nodes === \"number\" ? indexResponse.nodes : 0;\n const edgeCount = typeof indexResponse.edges === \"number\" ? indexResponse.edges : 0;\n\n // Warn if counts were not numbers and defaulted to zero\n if (typeof indexResponse.nodes !== \"number\") {\n handleWarning(\n \"Invalid node count from index_repository\",\n [\n `Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,\n \"Defaulting to 0 - index may be incomplete or corrupted\",\n ]\n );\n }\n\n if (typeof indexResponse.edges !== \"number\") {\n handleWarning(\n \"Invalid edge count from index_repository\",\n [\n `Expected number, got ${typeof indexResponse.edges}: ${String(indexResponse.edges)}`,\n \"Defaulting to 0 - index may be incomplete or corrupted\",\n ]\n );\n }\n\n // Write index metadata\n const meta: IndexMeta = {\n git_head: gitHead,\n timestamp,\n node_count: nodeCount,\n edge_count: edgeCount,\n };\n\n const analyzerName = this.mapper.getAnalyzerName();\n await writeIndexMeta(meta, projectRoot, analyzerName);\n\n return {\n node_count: nodeCount,\n edge_count: edgeCount,\n git_head: gitHead,\n timestamp,\n };\n } finally {\n client.close();\n }\n }\n\n /**\n * Fetch Route nodes with their HANDLES-edge handlers attached.\n *\n * CBM Route nodes carry only `{ method, source }` and often no file (every fastapi route,\n * for example); the handler function, its symbol, lines and file come from HANDLES edges.\n * If the handler query fails the routes are returned as they are.\n *\n * @private\n */\n private async fetchRoutes(tools: CbmTools, projectName: string, label: string): Promise<CbmGraphNode[]> {\n const routes = await tools.searchGraph({ label, project: projectName });\n try {\n const handlers = routeHandlersFromQuery(await tools.queryGraph(ROUTE_HANDLERS_QUERY, projectName));\n return attachRouteHandlers(routes, handlers);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Could not read route handlers (HANDLES edges); routes lack handler details\", [\n `Error: ${errorMsg}`,\n ]);\n return routes;\n }\n }\n\n /**\n * Resolve the internal project name used by codebase-memory-mcp for a given root path.\n *\n * list_projects returns { name, root_path } per entry. search_graph and other\n * query tools require the internal `name`, not the absolute `root_path`.\n * Falls back to the absolute path with a warning if the project is not found.\n */\n private async resolveProjectName(\n client: StdioClient | CbmTools,\n codebaseRoot: string\n ): Promise<string> {\n const tools = client instanceof CbmTools ? client : new CbmTools(client);\n const projects = await tools.listProjects();\n const match = projects.find((p) => p.root_path === codebaseRoot);\n if (!match) {\n handleWarning(\n \"Project not found in CBM backend; using absolute path as project identifier\",\n [\n `Codebase root: ${codebaseRoot}`,\n \"Run `dr analyzer index` to ensure the project is indexed\",\n ]\n );\n return codebaseRoot;\n }\n return match.name;\n }\n\n /**\n * Query for API endpoints/routes in the indexed project\n *\n * Searches the indexed graph for Route nodes, applies field mappings from\n * the analyzer's mapping, and filters based on test code exclusion rules.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of endpoint candidates with confidence and source info\n * @throws CLIError if the project is not indexed\n */\n async endpoints(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<EndpointCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Get the Route mapping BEFORE initializing client to validate it exists\n const routeMapping = this.mapper.getNodeMapping(\"Route\");\n if (!routeMapping) {\n throw new CLIError(\n \"Route mapping not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name for search_graph (requires name, not absolute path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Search for Route nodes using the CBM label from the mapping\n const allNodes = await this.fetchRoutes(tools, projectName, routeMapping.cbm_label);\n // Keep only nodes whose qualified_name follows the __route__METHOD__/path\n // convention. Nodes that don't match are graph artifacts (documentation\n // strings, base URLs, etc.) that were incidentally labelled Route by the\n // indexer \u2014 not actual HTTP routes.\n const nodes = allNodes\n .filter((n) => /^__route__[A-Z]+__\\//.test(String(n.qualified_name ?? \"\")))\n .filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? \"\")));\n\n // Transform nodes to endpoint candidates\n const candidates: EndpointCandidate[] = [];\n\n for (const node of nodes) {\n const candidate = await this.transformNodeToEndpoint(\n node,\n routeMapping,\n effectiveCodebaseRoot\n );\n\n // Skip unattributable candidates \u2014 empty source_file means the graph\n // found a path string in non-route code (test assertions, fixture data)\n if (!candidate.source_file) {\n continue;\n }\n // Apply test code exclusion filter\n if (!this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n\n // ISSUE-003: Client-side API consumer detection.\n // When no server-side Route nodes are found (e.g. React/frontend apps using fetch/axios),\n // fall back to querying Function nodes in API-client files whose names follow HTTP verb\n // conventions. These are low-confidence candidates \u2014 the user confirms and adds them.\n if (candidates.length === 0) {\n try {\n const clientQuery = `\n MATCH (n:Function)\n WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'\n OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'\n OR n.file_path CONTAINS 'service' OR n.file_path CONTAINS 'http'\n OR n.file_path CONTAINS 'Http')\n AND (n.name STARTS WITH 'fetch' OR n.name STARTS WITH 'get'\n OR n.name STARTS WITH 'post' OR n.name STARTS WITH 'put'\n OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'\n OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'\n OR n.name STARTS WITH 'list')\n RETURN ${nodeColumns(\"n\")}\n `;\n\n const clientNodes = nodesFromQuery(await tools.queryGraph(clientQuery, projectName));\n for (const node of clientNodes) {\n if (!node.file_path) continue;\n const candidate = this.shapeClientSideEndpoint(node, effectiveCodebaseRoot);\n if (candidate && !this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n } catch (error) {\n // query_graph not available \u2014 skip the client-side fallback\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Client-side endpoint fallback skipped: query_graph failed\", [`Error: ${errorMsg}`]);\n }\n }\n\n this.warnOnDuplicateIdFragments(\n candidates,\n (c) => c.suggested_id_fragment,\n (c) => c.source_symbol || c.source_file || \"(unknown)\"\n );\n return candidates;\n } finally {\n client.close();\n }\n }\n\n /**\n * Execute a raw Cypher query against the analyzer's graph\n *\n * Passes the raw Cypher string directly to the query_graph MCP tool.\n * The query_graph tool is optional (not in required_tools contract).\n * If the tool call fails, surfaces a clear error message.\n *\n * @param projectRoot Absolute path to the project root\n * @param rawQuery Raw Cypher query string\n * @param options Optional codebase configuration\n * @returns Raw result from query_graph tool\n * @throws CLIError if project not indexed or query_graph tool unavailable\n */\n async query(projectRoot: string, rawQuery: string, options?: AnalyzerCodebaseOptions): Promise<unknown> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed (same pre-flight checks as endpoints)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name \u2014 query_graph requires the CBM name, not the absolute path\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Call query_graph tool; all result pages are merged into one { columns, rows }\n try {\n const result = await tools.queryGraph(rawQuery, projectName);\n return { ...result, total: result.rows.length };\n } catch (error) {\n // CBM rejected the query (syntax error, unsupported clause): surface its message\n if (error instanceof CbmToolError) {\n throw new CLIError(`Query failed: ${error.detail}`, ErrorCategory.USER, error.suggestions);\n }\n\n // Check if the error is due to the tool not being available\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes(\"query_graph\") || errorMessage.includes(\"Unknown method\")) {\n throw new CLIError(\n \"query_graph not supported by this analyzer\",\n ErrorCategory.USER,\n [\n \"The query_graph tool is optional in the CBM analyzer contract\",\n \"Ensure your codebase-memory-mcp version supports raw graph queries\",\n \"Contact the analyzer maintainers if you need this feature\",\n ]\n );\n }\n\n // Re-throw other errors\n throw error;\n }\n } finally {\n client.close();\n }\n }\n\n private warnOnDuplicateIdFragments<T>(\n items: T[],\n getKey: (item: T) => string | undefined,\n getLabel: (item: T) => string\n ): void {\n const seen = new Map<string, string>();\n for (const item of items) {\n const key = getKey(item);\n if (!key) continue;\n const label = getLabel(item);\n if (seen.has(key)) {\n handleWarning(\n `Duplicate suggested ID \"${key}\" \u2014 multiple symbols map to the same element ID`,\n [\n `First: ${seen.get(key)!}`,\n `Conflict: ${label}`,\n `Rename one or use \"dr add\" with a custom name`,\n ]\n );\n } else {\n seen.set(key, label);\n }\n }\n }\n\n /**\n * Transform a CBM graph node to an endpoint candidate\n *\n * Maps CBM Route node properties to DR EndpointCandidate contract:\n * - suggested_layer: always \"api\" for endpoints\n * - suggested_element_type: always \"operation\" for routes\n * - suggested_name: from node properties or id (kebab-case)\n * - http_method: from properties.method or defaults to \"GET\"\n * - http_path: from properties.path or defaults to \"/\"\n * - handler_qualified_name: from properties.handler_name or empty string\n * - source_symbol: from properties.symbol or empty string\n * - source_start_line: from properties.start_line or 0\n * - source_end_line: from properties.end_line or 0\n *\n * Downgrades confidence if required fields are missing.\n *\n * @private\n */\n private async transformNodeToEndpoint(\n node: CbmGraphNode,\n mapping: AnalyzerNodeMapping,\n codebaseRoot: string\n ): Promise<EndpointCandidate> {\n const properties = node.properties ?? {};\n // Node identifier for display \u2014 actual codebase-memory-mcp nodes use 'name', not 'id'\n const nodeId = String(node.name ?? node.id ?? \"(unknown)\");\n\n // Extract base fields\n let confidence = mapping.confidence as \"high\" | \"medium\" | \"low\";\n\n // Suggested name in kebab-case: properties.name \u2192 node.name \u2192 node.id\n const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? \"\"));\n\n // Suggested ID fragment (same as name for endpoints)\n const suggestedIdFragment = suggestedName;\n\n // Required fields for dr add api operation\n // Extract HTTP method: properties first, then parse qualified_name pattern __route__METHOD__/path\n let rawMethod: string;\n if (typeof properties.method === \"string\") {\n rawMethod = properties.method.toUpperCase();\n } else {\n const qn = String(node.qualified_name ?? \"\");\n const methodMatch = qn.match(/^__route__([A-Z]+)__/);\n rawMethod = methodMatch?.[1] ?? \"GET\";\n if (rawMethod === \"ANY\") rawMethod = \"GET\";\n }\n\n // Validate against valid HTTP methods using the constant\n const validMethods = new Set(VALID_HTTP_METHODS);\n const isValidMethod = validMethods.has(rawMethod as HttpMethod);\n const httpMethod: HttpMethod = isValidMethod\n ? (rawMethod as HttpMethod)\n : \"GET\";\n\n if (!isValidMethod) {\n handleWarning(\n `Node ${nodeId}: Invalid HTTP method`,\n [\n `Method '${rawMethod}' is not a valid HTTP method`,\n `Valid methods: ${Array.from(validMethods).join(\", \")}`,\n \"Defaulting to GET - endpoint documentation may be inaccurate\",\n ]\n );\n }\n\n // Extract HTTP path: properties first, then node.name (which IS the URL path for flat nodes)\n let httpPath: string;\n if (typeof properties.path === \"string\") {\n httpPath = properties.path;\n } else if (typeof node.name === \"string\" && node.name.startsWith(\"/\")) {\n httpPath = node.name;\n } else {\n handleWarning(\n `Node ${nodeId}: Missing or invalid HTTP path`,\n [\n `Expected string, got ${typeof properties.path}`,\n \"Defaulting to / - endpoint may be incomplete\",\n ]\n );\n httpPath = \"/\";\n }\n\n // Handler information \u2014 try multiple property names because CBM indexers vary.\n // Do NOT fall back to properties.qualified_name \u2014 that is the route node's own QN.\n const handlerQualifiedName = String(\n properties.handler_name ??\n properties.handler_qualified_name ??\n \"\"\n );\n const sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? \"\");\n const sourceStartLine = Number(properties.start_line ?? 0);\n const sourceEndLine = Number(properties.end_line ?? 0);\n\n // Downgrade confidence if handler information is missing\n if (!handlerQualifiedName || !sourceSymbol) {\n confidence = capConfidence(confidence);\n }\n\n // Extract source file \u2014 check both node-level and properties-level fields\n let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? \"\");\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path for ${nodeId}`,\n [\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // sourceFile keeps its original absolute value\n }\n }\n\n return {\n source_file: sourceFile,\n confidence,\n suggested_layer: \"api\",\n suggested_element_type: \"operation\",\n suggested_name: suggestedName,\n suggested_id_fragment: suggestedIdFragment,\n http_method: httpMethod,\n http_path: httpPath,\n handler_qualified_name: handlerQualifiedName,\n source_symbol: sourceSymbol,\n source_start_line: sourceStartLine,\n source_end_line: sourceEndLine,\n source_reference: {\n provenance: \"extracted\",\n locations: sourceFile\n ? [\n {\n file: sourceFile,\n symbol: sourceSymbol || undefined,\n },\n ]\n : undefined,\n },\n };\n }\n\n\n /**\n * Check if a candidate (endpoint, service, or datastore) is in test code\n *\n * Applies the test_code_exclusion filtering rule from the analyzer mapping.\n * If the configured regex pattern is invalid, warns the user once and falls back to defaults.\n * Uses a one-shot flag to ensure the warning is only emitted once even if called in a loop.\n *\n * Accepts any object with a source_file property (structural typing), allowing it to work\n * with EndpointCandidate, ServiceCandidate, DatastoreCandidate, and temporary objects.\n *\n * @private\n */\n\n /**\n * Build a low-confidence EndpointCandidate from a client-side API function node.\n * Used as a fallback when no server-side Route nodes are found (ISSUE-003).\n */\n private shapeClientSideEndpoint(node: CbmGraphNode, codebaseRoot: string): EndpointCandidate | null {\n const name = String(node.name ?? node.id ?? \"\");\n if (!name) return null;\n\n let sourceFile = String(node.file_path ?? \"\");\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch {\n // keep absolute path on failure\n }\n }\n if (!sourceFile) return null;\n\n const httpMethod = inferHttpMethodFromFunctionName(name);\n const httpPath = inferHttpPathFromFunctionName(name);\n const suggestedName = toKebabCase(name);\n\n return {\n source_file: sourceFile,\n confidence: \"low\",\n suggested_layer: \"api\",\n suggested_element_type: \"operation\",\n suggested_name: suggestedName,\n suggested_id_fragment: suggestedName,\n http_method: httpMethod,\n http_path: httpPath,\n handler_qualified_name: name,\n source_symbol: name,\n source_start_line: Number(node.properties?.start_line ?? 0),\n source_end_line: Number(node.properties?.end_line ?? 0),\n source_reference: {\n provenance: \"inferred\",\n locations: [{ file: sourceFile, symbol: name }],\n },\n };\n }\n\n private isTestCode(candidate: { source_file: string }): boolean {\n // Get filtering rules from the analyzer mapping\n const filteringRules = this.mapper.getFilteringRules();\n\n // Find the test_code_exclusion rule\n const testCodeRule = filteringRules.find(\n (rule) => rule.name === \"test_code_exclusion\" && rule.enabled\n );\n\n // If no test_code_exclusion rule found, apply default patterns as fallback\n if (!testCodeRule || !testCodeRule.pattern) {\n return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) =>\n pattern.test(candidate.source_file)\n );\n }\n\n // Apply the pattern from the filtering rule\n try {\n const testCodePattern = new RegExp(testCodeRule.pattern);\n return testCodePattern.test(candidate.source_file);\n } catch (error) {\n // If regex is invalid, warn user once (not on every invocation in the loop)\n // and fall back to default patterns\n if (!this.testCodePatternWarned) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Invalid test_code_exclusion regex pattern in analyzer mapping\",\n [\n `Pattern: ${testCodeRule.pattern}`,\n `Error: ${errorMsg}`,\n \"Falling back to default test code patterns\",\n ]\n );\n this.testCodePatternWarned = true;\n }\n return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) =>\n pattern.test(candidate.source_file)\n );\n }\n }\n\n /**\n * Query for services/components in the indexed project\n *\n * Searches for application-layer nodes using the mapping-loader-driven pattern.\n * For each node, evaluates all promotion_heuristics and records which fire in\n * qualifying_heuristics. Drops candidates with zero qualifying heuristics (unless\n * is_entry_point heuristic fires, adding it to qualifying_heuristics). Caps confidence\n * at \"medium\" and excludes test files.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of service candidates with qualifying heuristics populated\n * @throws CLIError if the project is not indexed\n */\n async services(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<ServiceCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Get application-layer node labels from the mapping (pattern from endpoints())\n const nodeLabels = this.mapper.getNodeLabels();\n const applicationLabels: string[] = [];\n\n // Filter to only application-layer labels by checking their mapping\n for (const label of nodeLabels) {\n const mapping = this.mapper.getNodeMapping(label);\n // Only behavioural and structural types are service sources; data-object labels\n // (Interface, Type, Enum, ...) never become service candidates.\n if (mapping && mapping.dr_layer === \"application\" && SERVICE_SOURCE_TYPES.has(mapping.dr_element_type)) {\n applicationLabels.push(label);\n }\n }\n\n // If no application-layer labels found, warn and return empty list\n if (applicationLabels.length === 0) {\n handleWarning(\n \"No application-layer labels found in analyzer mapping\",\n [\n \"Check that the analyzer is properly configured with application-layer node mappings\",\n \"Services detection requires at least one mapped application-layer node type\",\n \"Returning empty services list\"\n ]\n );\n return [];\n }\n\n const candidates: ServiceCandidate[] = [];\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name once before the label search loop\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Process high-fidelity node types first so the source_file dedup (below) retains\n // class/module level entries over method-level entries from the same file.\n const labelPriority: Record<string, number> = { Class: 0, Module: 1 };\n applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));\n\n // Search for each application-layer label\n for (const label of applicationLabels) {\n const nodes = await tools.searchGraph({ label, project: projectName });\n\n // Get the node mapping for this label\n const nodeMapping = this.mapper.getNodeMapping(label);\n if (!nodeMapping) {\n continue;\n }\n\n // Get promotion heuristics from the mapping\n const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];\n\n // Transform nodes to service candidates\n for (const node of nodes) {\n const candidate = await this.transformNodeToService(\n node,\n nodeMapping,\n effectiveCodebaseRoot,\n promotionHeuristicNames\n );\n\n // Filter out candidates with zero qualifying heuristics (unless is_entry_point is true)\n // and apply test code exclusion filter\n if (candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n }\n\n // Deduplicate by source_file \u2014 the model documents at class/file granularity;\n // method-level entries from the same file are noise.\n const seenFiles = new Set<string>();\n const deduped = candidates.filter((c) => {\n if (!c.source_file) return true;\n if (seenFiles.has(c.source_file)) return false;\n seenFiles.add(c.source_file);\n return true;\n });\n this.warnOnDuplicateIdFragments(\n deduped,\n (c) => c.suggested_id_fragment,\n (c) => c.source_symbol || c.source_file || \"(unknown)\"\n );\n return deduped;\n } finally {\n client.close();\n }\n }\n\n /**\n * Transform a CBM graph node to a service candidate\n *\n * Evaluates all promotion heuristics against the node's properties and records\n * which heuristics fire. Drops candidates where no heuristics fire (unless the\n * is_entry_point heuristic fires, which adds it to qualifying_heuristics).\n * Caps confidence at \"medium\".\n *\n * @private\n */\n private async transformNodeToService(\n node: CbmGraphNode,\n mapping: AnalyzerNodeMapping,\n codebaseRoot: string,\n promotionHeuristicNames: string[]\n ): Promise<ServiceCandidate> {\n const properties = node.properties ?? {};\n\n // Suggested name in kebab-case: properties.name \u2192 node.name \u2192 node.id\n const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? \"\"));\n\n // Suggested ID fragment (same as name for services)\n const suggestedIdFragment = suggestedName;\n\n // Extract source file (relative to codebase root)\n let sourceFile = node.file_path ?? \"\";\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path for ${String(node.name ?? node.id ?? \"(unknown)\")}`,\n [\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // sourceFile keeps its original absolute value\n }\n }\n\n // Extract qualified name: properties first, then node.qualified_name, then node.id\n const qualifiedName = String(properties.qualified_name ?? node.qualified_name ?? node.name ?? node.id ?? \"\");\n\n // Extract fan-in and fan-out metrics\n const fanIn = typeof properties.fan_in === \"number\" ? properties.fan_in : 0;\n const fanOut = typeof properties.fan_out === \"number\" ? properties.fan_out : 0;\n\n // Evaluate all promotion heuristics and collect which ones fire\n const qualifyingHeuristics: string[] = [];\n let isEntryPoint = false;\n\n for (const heuristicName of promotionHeuristicNames) {\n const heuristic = this.mapper.getHeuristic(heuristicName);\n if (!heuristic) {\n // Warn about missing heuristic only once per heuristic name\n if (!this.warnedHeuristics.has(heuristicName)) {\n this.warnedHeuristics.add(heuristicName);\n handleWarning(\n `Promotion heuristic \"${heuristicName}\" not found in analyzer mapping`,\n [\n \"Check the mapping configuration for typos\",\n `Available heuristics: ${getKnownHeuristicNames().join(\", \")}`,\n \"This heuristic will be skipped for service promotion\",\n ]\n );\n }\n continue;\n }\n\n // Evaluate the heuristic against the node\n const fires = this.evaluateHeuristic(heuristic, node, sourceFile);\n if (fires) {\n qualifyingHeuristics.push(heuristicName);\n // Track if is_entry_point heuristic fired\n if (heuristicName === \"is_entry_point\") {\n isEntryPoint = true;\n }\n }\n }\n\n // Drop candidates where zero heuristics fire AND is_entry_point is not true\n const shouldKeep = qualifyingHeuristics.length > 0 || isEntryPoint;\n if (!shouldKeep) {\n // Return a minimal candidate with empty qualifying_heuristics\n // The services() method will filter this out based on qualifying_heuristics.length > 0 check\n const elementType: \"applicationservice\" | \"applicationcomponent\" =\n mapping.dr_element_type_promoted === \"applicationcomponent\"\n ? \"applicationcomponent\"\n : \"applicationservice\";\n return {\n suggested_layer: \"application\",\n suggested_element_type: elementType,\n suggested_id_fragment: suggestedIdFragment,\n suggested_name: suggestedName,\n source_file: sourceFile,\n source_symbol: String(properties.name ?? node.name ?? node.id ?? \"\"),\n qualified_name: qualifiedName,\n qualifying_heuristics: [],\n confidence: \"low\",\n fan_in: fanIn,\n fan_out: fanOut,\n };\n }\n\n // Cap confidence at \"medium\" (from mapping or default to medium)\n const confidence = capConfidence(\n mapping.confidence as \"high\" | \"medium\" | \"low\"\n );\n\n // Ensure element type is one of the valid values\n const elementType: \"applicationservice\" | \"applicationcomponent\" =\n mapping.dr_element_type_promoted === \"applicationcomponent\"\n ? \"applicationcomponent\"\n : \"applicationservice\";\n\n return {\n suggested_layer: \"application\",\n suggested_element_type: elementType,\n suggested_id_fragment: suggestedIdFragment,\n suggested_name: suggestedName,\n source_file: sourceFile,\n source_symbol: String(properties.name ?? node.name ?? node.id ?? \"\"),\n qualified_name: qualifiedName,\n qualifying_heuristics: qualifyingHeuristics,\n confidence,\n fan_in: fanIn,\n fan_out: fanOut,\n };\n }\n\n /**\n * Evaluate a single heuristic against a node\n *\n * Checks if the heuristic's conditions are met based on the node's properties\n * and file path. Unknown heuristics are logged with a warning rather than\n * silently ignored, to support dynamic heuristics from cbm.json.\n *\n * Warnings for unknown heuristics are deduplicated - each heuristic name is warned\n * about only once, even if evaluated against many nodes.\n *\n * @private\n */\n private evaluateHeuristic(\n heuristic: AnalyzerHeuristic,\n node: CbmGraphNode,\n sourceFile: string\n ): boolean {\n const properties = node.properties ?? {};\n\n // Use the heuristic utility function\n const result = evaluateHeuristic(heuristic, properties, sourceFile);\n\n // For unknown heuristics, log a warning (deduplicated by heuristic name)\n if (heuristic.name && !getKnownHeuristicNames().includes(heuristic.name)) {\n // Only warn once per heuristic name\n if (!this.warnedHeuristics.has(heuristic.name)) {\n handleWarning(\n `Unknown heuristic type: ${heuristic.name}`,\n [\n \"The heuristic is defined in cbm.json but not implemented in heuristic-utils.ts\",\n \"This heuristic will be treated as not met (returning false)\",\n \"Add support for this heuristic to evaluateHeuristic() if it should fire\",\n ]\n );\n this.warnedHeuristics.add(heuristic.name);\n }\n }\n\n return result;\n }\n\n /**\n * Match a file path against a glob-like pattern\n *\n * @param filePath The file path to match\n * @param pattern The glob pattern\n * @returns True if the pattern matches, false otherwise\n * @private\n */\n private matchPattern(filePath: string, pattern: string): boolean {\n return matchPattern(filePath, pattern);\n }\n\n /**\n * Query for datastores/databases inferred from code analysis\n *\n * Applies datastore_detection heuristic rules to aggregate signals by file/module.\n * Matches import target names against import_patterns and function/symbol names against\n * naming_indicators from the heuristic parameters. All candidates have confidence \"low\".\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of datastore candidates aggregated by inferred datastore\n * @throws CLIError if the project is not indexed\n */\n async datastores(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<DatastoreCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Load the datastore_detection heuristic\n const datastoreHeuristic = this.mapper.getHeuristic(\"datastore_detection\");\n if (!datastoreHeuristic) {\n throw new CLIError(\n \"datastore_detection heuristic not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\n \"The analyzer configuration is missing the datastore_detection heuristic\",\n \"Run `npm run build:spec` to recompile the analyzer artifacts\"\n ]\n );\n }\n\n const importPatterns = datastoreHeuristic.parameters?.patterns ?? [];\n const namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [];\n\n // Map to track signals by datastore name\n const datastoreSignals = new Map<\n string,\n Array<{\n sourceFile: string;\n importPattern?: string;\n functionPatterns: string[];\n }>\n >();\n // Tracks the first import target that produced each normalized datastore name,\n // so we can warn when distinct imports collapse to the same element ID.\n const datastoreFirstSource = new Map<string, string>();\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name once (search_graph and query_graph require name, not path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Try to use query_graph to traverse IMPORTS edges (1 call instead of many)\n // This implements the spec algorithm requirement for IMPORTS edge traversal\n let allNodes: CbmGraphNode[] = [];\n try {\n // Query for all IMPORTS edges and their connected nodes\n const importsQuery = `\n MATCH (n1)-[r:IMPORTS]->(n2)\n RETURN DISTINCT ${nodeColumns(\"n1\")}\n UNION\n MATCH (n1)-[r:IMPORTS]->(n2)\n RETURN DISTINCT ${nodeColumns(\"n2\")}\n `;\n\n allNodes = nodesFromQuery(await tools.queryGraph(importsQuery, projectName));\n } catch (error) {\n // Fallback to search_graph if query_graph is unavailable\n // Warn the user that we're using degraded search semantics\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"query_graph tool failed; falling back to search_graph for datastore detection\",\n [\n `Error: ${errorMsg}`,\n \"Using search_graph will return ALL nodes instead of only IMPORTS-connected nodes\",\n \"Datastore detection results may include false positives or be less accurate\",\n \"Ensure query_graph is supported by your analyzer version for best results\"\n ]\n );\n\n // This provides backward compatibility while still attempting IMPORTS traversal\n const nodeLabels = this.mapper.getNodeLabels();\n for (const label of nodeLabels) {\n allNodes.push(...(await tools.searchGraph({ label, project: projectName })));\n }\n }\n\n // Process all nodes to find datastore signals\n for (const node of allNodes) {\n const filePath = node.file_path ?? \"\";\n\n // For import target detection, check the node's import target name (from label/properties)\n // not the file path. IMPORTS edges connect to target nodes like \"mongodb\", \"pg\"\n const importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? \"\").toLowerCase();\n\n // Check if import target matches datastore-related patterns\n let matchedImportPattern: string | undefined;\n for (const pattern of importPatterns) {\n if (this.matchPattern(importTargetName, pattern)) {\n matchedImportPattern = pattern;\n break;\n }\n }\n\n // Check if node name contains datastore naming indicators\n const nodeName = String(node.properties?.name ?? \"\").toLowerCase();\n const matchedIndicators = namingIndicators.filter((indicator) =>\n nodeName.includes(indicator.toLowerCase())\n );\n\n // Skip if no import pattern matched AND no naming indicators matched\n if (!matchedImportPattern && matchedIndicators.length === 0) {\n continue;\n }\n\n // Extract datastore name from import target name or node properties\n let datastoreName = this.inferDatastoreName(\n filePath,\n node,\n matchedImportPattern,\n importTargetName\n );\n if (!datastoreName) {\n continue;\n }\n\n // Normalize to lowercase and replace special chars\n datastoreName = toKebabCase(datastoreName);\n\n // Get or create entry for this datastore; warn when distinct import targets\n // normalize to the same name (merging is intentional, but the collision is surfaced)\n if (!datastoreSignals.has(datastoreName)) {\n datastoreSignals.set(datastoreName, []);\n datastoreFirstSource.set(datastoreName, importTargetName);\n } else if (datastoreFirstSource.get(datastoreName) !== importTargetName) {\n handleWarning(\n `Multiple imports normalize to datastore name \"${datastoreName}\"`,\n [\n `First: ${datastoreFirstSource.get(datastoreName)!}`,\n `Conflict: ${importTargetName}`,\n `Use \"dr add\" with a custom name to distinguish them`,\n ]\n );\n }\n\n const signals = datastoreSignals.get(datastoreName)!;\n\n // Convert file path to relative\n let relativeFile = filePath;\n if (filePath && effectiveCodebaseRoot) {\n try {\n relativeFile = relativeToRoot(effectiveCodebaseRoot, filePath);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path in datastore detection`,\n [\n `File: ${filePath}`,\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // relativeFile keeps its original absolute value\n }\n }\n\n // Add signal for this source file\n signals.push({\n sourceFile: relativeFile,\n importPattern: matchedImportPattern,\n functionPatterns: matchedIndicators,\n });\n }\n\n // Transform collected signals into DatastoreCandidate objects\n const candidates: DatastoreCandidate[] = [];\n\n // Convert entries to array to avoid TypeScript MapIterator issues\n const entriesArray = Array.from(datastoreSignals.entries());\n for (const [datastoreName, signals] of entriesArray) {\n // Group by source file and aggregate evidence\n const inferredFromMap = new Map<\n string,\n {\n importPatterns: Set<string>;\n functionPatterns: Set<string>;\n }\n >();\n\n for (const signal of signals) {\n if (!inferredFromMap.has(signal.sourceFile)) {\n inferredFromMap.set(signal.sourceFile, {\n importPatterns: new Set(),\n functionPatterns: new Set(),\n });\n }\n\n const entry = inferredFromMap.get(signal.sourceFile)!;\n if (signal.importPattern) {\n entry.importPatterns.add(signal.importPattern);\n }\n signal.functionPatterns.forEach((fp) => entry.functionPatterns.add(fp));\n }\n\n // Create candidate with aggregated evidence\n let inferredFromMapEntries = Array.from(inferredFromMap.entries());\n\n // Apply test code exclusion filter: remove test files from inferred_from\n inferredFromMapEntries = inferredFromMapEntries.filter(([sourceFile]) => {\n // Create a temporary object with source_file to reuse isTestCode()\n const tempCandidate = {\n source_file: sourceFile,\n };\n return !this.isTestCode(tempCandidate);\n });\n\n // Skip candidates where all sources are test code\n if (inferredFromMapEntries.length === 0) {\n continue;\n }\n\n const inferredFrom = inferredFromMapEntries.map(\n ([sourceFile, evidence]) => ({\n source_file: sourceFile,\n import_pattern: Array.from(evidence.importPatterns).join(\", \") || datastoreName,\n function_patterns: Array.from(evidence.functionPatterns),\n })\n );\n\n candidates.push({\n suggested_layer: \"data-store\",\n suggested_name: datastoreName,\n inferred_from: inferredFrom,\n confidence: \"low\",\n notes: `Inferred from ${inferredFrom.length} source file(s) based on datastore detection heuristics`,\n });\n }\n\n // ISSUE-003: Browser storage detection.\n // localStorage and sessionStorage are global browser APIs \u2014 they don't appear in the\n // IMPORTS graph. Query for Function nodes in files that are commonly associated with\n // browser-side state persistence (store files, auth files, preference files).\n const browserStorageTypes = [\"local-storage\", \"session-storage\"] as const;\n const browserStorageSignals = new Map<string, Set<string>>();\n\n try {\n const storageQuery = `\n MATCH (n:Function)\n WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'\n OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'\n OR n.file_path CONTAINS 'auth' OR n.file_path CONTAINS 'Auth'\n OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'\n OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'\n OR n.file_path CONTAINS 'settings' OR n.file_path CONTAINS 'Settings'\n RETURN ${nodeColumns(\"n\")}\n `;\n\n const storageNodes = nodesFromQuery(await tools.queryGraph(storageQuery, projectName));\n for (const node of storageNodes) {\n if (!node.file_path) continue;\n\n const relFile = relativeToRoot(effectiveCodebaseRoot, node.file_path);\n\n // Heuristic: files with 'session' in name likely use sessionStorage;\n // otherwise assume localStorage (more common pattern).\n const storageKey = /session/i.test(relFile) ? \"session-storage\" : \"local-storage\";\n if (!browserStorageSignals.has(storageKey)) {\n browserStorageSignals.set(storageKey, new Set());\n }\n browserStorageSignals.get(storageKey)!.add(relFile);\n }\n } catch {\n // query_graph not available \u2014 skip browser storage detection silently\n }\n\n for (const storageType of browserStorageTypes) {\n const sourceFiles = browserStorageSignals.get(storageType);\n if (!sourceFiles || sourceFiles.size === 0) continue;\n\n const accessPattern = storageType === \"local-storage\" ? \"localStorage\" : \"sessionStorage\";\n const inferredFrom = Array.from(sourceFiles)\n .filter((f) => !this.isTestCode({ source_file: f }))\n .map((f) => ({\n source_file: f,\n import_pattern: accessPattern,\n function_patterns: [],\n }));\n\n if (inferredFrom.length > 0) {\n candidates.push({\n suggested_layer: \"data-store\",\n suggested_name: storageType,\n inferred_from: inferredFrom,\n confidence: \"low\",\n notes: `Inferred from ${inferredFrom.length} source file(s) using browser ${accessPattern} (global API, no import required)`,\n });\n }\n }\n\n return candidates;\n } finally {\n client.close();\n }\n }\n\n /**\n * Infer a datastore name from import target name, file path, node properties, or import pattern\n *\n * @private\n */\n private inferDatastoreName(\n filePath: string,\n node: CbmGraphNode,\n matchedPattern?: string,\n importTargetName?: string\n ): string | undefined {\n // Priority 1: Use import target name directly if it matched a pattern\n // This is the most reliable signal for import-based detection (e.g., \"mongodb\", \"pg\")\n if (importTargetName && matchedPattern) {\n // If the import target matches the pattern, use it as-is\n if (this.matchPattern(importTargetName, matchedPattern)) {\n return importTargetName;\n }\n }\n\n // Priority 2: Try to extract from import pattern (e.g., \"mongodb\" from pattern)\n if (matchedPattern) {\n // Extract database name from pattern like \"mongodb\", \"pg\", etc.\n const match = matchedPattern.match(/[a-z]+/i);\n if (match) {\n return match[0];\n }\n }\n\n // Priority 3: Try to extract from file name (e.g., \"users.migration.ts\" -> \"users\")\n const fileName = path.basename(filePath);\n if (fileName.includes(\"migration\") || fileName.includes(\"schema\")) {\n const match = fileName.match(/^([a-z0-9_-]+)/i);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n // Priority 4: Try to extract from node name or label\n const nodeName = String(node.label ?? node.id ?? \"\");\n\n // Check if the label is a known datastore-related type by checking mapping\n // Generic structural labels like Function, Method, Class, Module, Route should be excluded\n if (nodeName) {\n const nodeMapping = this.mapper.getNodeMapping(nodeName);\n // If the label is in the mapping and NOT a generic structural type, use it\n if (nodeMapping && nodeMapping.dr_layer !== \"api\" && nodeMapping.dr_layer !== \"application\") {\n return nodeName;\n }\n\n // Also check if it explicitly looks like a datastore name (e.g., \"Database\", \"Repository\")\n if (nodeName.toLowerCase().includes(\"database\") ||\n nodeName.toLowerCase().includes(\"repository\") ||\n nodeName.toLowerCase().includes(\"datastore\")) {\n return nodeName;\n }\n }\n\n // Fallback: use a generic name based on what was detected\n return undefined;\n }\n\n /**\n * Query for callers of a specific function or symbol\n *\n * Delegates to trace_path MCP tool with direction=\"inbound\" to find\n * all functions that call the specified qualified name. Applies depth clamping\n * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects\n * using edge-type mappings from the analyzer.\n *\n * @param projectRoot Absolute path to the project root\n * @param qualifiedName Fully qualified symbol name (e.g., \"com.example.UserService.getUser\")\n * @param depth Maximum traversal depth (default 3, clamped to max 10)\n * @param options Optional codebase configuration\n * @returns Array of callers in the call graph\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async callers(\n projectRoot: string,\n qualifiedName: string,\n depth?: number,\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n return this.traceCallPath(projectRoot, qualifiedName, depth, \"inbound\", options);\n }\n\n /**\n * Query for callees of a specific function or symbol\n *\n * Delegates to trace_path MCP tool with direction=\"outbound\" to find\n * all functions called by the specified qualified name. Applies depth clamping\n * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects\n * using edge-type mappings from the analyzer.\n *\n * @param projectRoot Absolute path to the project root\n * @param qualifiedName Fully qualified symbol name (e.g., \"com.example.UserService.getUser\")\n * @param depth Maximum traversal depth (default 3, clamped to max 10)\n * @param options Optional codebase configuration\n * @returns Array of callees in the call graph\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async callees(\n projectRoot: string,\n qualifiedName: string,\n depth?: number,\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n return this.traceCallPath(projectRoot, qualifiedName, depth, \"outbound\", options);\n }\n\n /**\n * Cypher fallback for call graph traversal when trace_path is unavailable.\n *\n * Uses query_graph directly with CALLS-edge Cypher when trace_path returns a\n * non-array nodes field (i.e. is not implemented in the binary).\n *\n * @private\n */\n private async callGraphViaCypher(\n binaryPath: string,\n projectName: string,\n qualifiedName: string,\n direction: \"outbound\" | \"inbound\",\n depth: number,\n codebaseRoot: string\n ): Promise<CallGraphNode[]> {\n // Escape single quotes for Cypher string literals (ISO SQL-style doubling)\n const safeQN = qualifiedName.replace(/'/g, \"''\");\n const limit = Math.min(depth * 20, 100);\n const cypher = direction === \"inbound\"\n ? `MATCH (caller)-[:CALLS]->(target) WHERE target.qualified_name = '${safeQN}' OR target.name = '${safeQN}' RETURN caller.id AS id, caller.qualified_name AS qualified_name, caller.file_path AS file_path LIMIT ${limit}`\n : `MATCH (source)-[:CALLS]->(callee) WHERE source.qualified_name = '${safeQN}' OR source.name = '${safeQN}' RETURN callee.id AS id, callee.qualified_name AS qualified_name, callee.file_path AS file_path LIMIT ${limit}`;\n\n if (depth > 1) {\n handleWarning(\n `Cypher CALLS fallback: depth ${depth} requested but fallback only traverses 1 hop`,\n [\"Multi-hop traversal requires trace_path support in the analyzer binary\"]\n );\n }\n\n // Spawn a fresh client \u2014 the binary closes the stdio pipe after an unimplemented\n // method call, so the caller's trace_path connection cannot be reused here.\n const client = new StdioClient();\n try {\n client.spawn(binaryPath);\n const version = await getCliVersion();\n await client.initialize({ name: \"dr-cli\", version });\n\n const queryResponse = await new CbmTools(client).queryGraph(cypher, projectName);\n\n const cols: string[] = queryResponse.columns;\n const tuples: unknown[][] = queryResponse.rows;\n return tuples.map((row) => {\n const obj: Record<string, unknown> = {};\n cols.forEach((col, i) => {\n // Strip table-alias prefix (e.g. \"caller.id\" \u2192 \"id\")\n const key = col.includes(\".\") ? col.slice(col.lastIndexOf(\".\") + 1) : col;\n obj[key] = row[i];\n });\n return shapeCallGraphNode(obj, codebaseRoot);\n });\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Cypher CALLS fallback failed \u2014 returning empty call graph\",\n [\n `Error: ${errorMsg}`,\n \"Neither trace_path nor query_graph is available for call graph traversal\",\n ]\n );\n return [];\n } finally {\n client.close();\n }\n }\n\n /**\n * Internal helper to trace call paths in either direction\n *\n * Handles common logic for callers() and callees(): pre-flight checks,\n * depth clamping, tool invocation, and response transformation.\n *\n * @private\n */\n private async traceCallPath(\n projectRoot: string,\n qualifiedName: string,\n depth: number | undefined,\n direction: \"outbound\" | \"inbound\",\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed (same pre-flight checks as endpoints)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Clamp depth: default 3, max 10\n const clampedDepth = clampDepth(depth);\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name \u2014 trace_path requires the CBM name, not the absolute path\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Call trace_path with the clamped depth\n let trace;\n try {\n trace = await tools.tracePath({\n function_name: qualifiedName,\n direction,\n depth: clampedDepth,\n project: projectName,\n });\n } catch (error) {\n if (error instanceof CbmToolError && /not found/i.test(error.detail)) {\n throw new CLIError(\n `Symbol not found in the code graph: ${qualifiedName}`,\n ErrorCategory.NOT_FOUND,\n [\n ...(error.suggestions ?? []),\n \"Pass the fully qualified name as CBM records it (see `dr analyzer services --json`)\",\n ]\n );\n }\n throw error;\n }\n\n if (!trace) {\n // trace_path not implemented \u2014 fall back to Cypher CALLS query.\n // Pass binary_path (not client): the binary closes the pipe after an unimplemented call.\n return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, effectiveCodebaseRoot);\n }\n\n const nodes = trace.nodes;\n const edges = trace.edges;\n\n // CBM 0.11+ trace rows carry qualified names and hops only; look up their files.\n if (trace.shape === \"grouped\" && nodes.length > 0) {\n try {\n const files = await tools.filePathsOf(\n nodes.map((n) => String(n.qualified_name ?? \"\")),\n projectName\n );\n for (const node of nodes) {\n node.file_path ??= files.get(String(node.qualified_name ?? \"\"));\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Could not look up source files for the call graph\", [`Error: ${errorMsg}`]);\n }\n }\n\n // Valid edge types come from the mapping; the first is the default for root nodes and\n // nodes without a typed incoming edge, which is CALLS regardless of the mapping's key order.\n const mappedEdgeTypes = this.mapper.getEdgeTypes();\n const validEdgeTypes = [\"CALLS\", ...mappedEdgeTypes.filter((t) => t !== \"CALLS\")];\n\n // Transform nodes to CallGraphNode objects\n const callGraphNodes: CallGraphNode[] = [];\n\n for (const node of nodes) {\n const nodeQualifiedName = String(node.qualified_name || node.id || \"\");\n\n // Shape the node using utility function\n let callGraphNode = shapeCallGraphNode(node, effectiveCodebaseRoot);\n\n // Determine edge type using utility function\n const nodeDepth = typeof node.depth === \"number\" ? node.depth : 0;\n const edgeType = determineEdgeType(\n nodeDepth,\n edges,\n nodeQualifiedName,\n validEdgeTypes\n );\n\n // Apply edge type to the shaped node\n callGraphNode.edge_type = edgeType;\n\n callGraphNodes.push(callGraphNode);\n }\n\n return callGraphNodes;\n } finally {\n client.close();\n }\n }\n\n /**\n * Shape a single CBM graph node into a route object with defaults applied\n *\n * This is extracted to be testable - applies the defaulting logic used in verify()\n * to ensure empty/missing properties are handled consistently.\n *\n * @param node Raw node from CBM graph search\n * @returns Shaped route object with defaults applied\n * @internal\n */\n shapeRoute(node: CbmGraphNode): {\n id: string;\n http_method: string;\n http_path: string;\n handler: string;\n source_file: string;\n source_symbol: string;\n } {\n const properties = node.properties ?? {};\n const nodeName = String(node.name ?? node.id ?? \"\");\n\n // Extract HTTP method: properties first, then parse from qualified_name\n // qualified_name format: __route__METHOD__/path (e.g. __route__GET__/users)\n let httpMethod = \"GET\";\n if (typeof properties.method === \"string\") {\n httpMethod = properties.method.toUpperCase();\n } else {\n const qn = String(node.qualified_name ?? \"\");\n const methodMatch = qn.match(/^__route__([A-Z]+)__/);\n const extracted = methodMatch?.[1] ?? \"\";\n if (extracted && (VALID_HTTP_METHODS as readonly string[]).includes(extracted)) {\n httpMethod = extracted;\n }\n }\n\n // Extract HTTP path: properties first, then node.name if it looks like a URL\n const httpPath = typeof properties.path === \"string\"\n ? properties.path\n : (typeof node.name === \"string\" && node.name.startsWith(\"/\") ? node.name : \"/\");\n\n const handler = String(properties.handler_name ?? \"\");\n const sourceFile = node.file_path ?? \"\";\n // Fall back to source_symbol then handler_name if symbol is absent (e.g. Python route handlers)\n const sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? properties.handler_name ?? \"\");\n\n // Construct route ID (use file:symbol as primary key for matching)\n const routeId = sourceFile && sourceSymbol\n ? `${sourceFile}:${sourceSymbol}`\n : (nodeName || node.id || \"(unknown)\");\n\n return {\n id: routeId,\n http_method: httpMethod,\n http_path: httpPath,\n handler,\n source_file: sourceFile,\n source_symbol: sourceSymbol,\n };\n }\n\n /**\n * Tier-0 classification of the indexed project (`dr analyzer classify --tier0-only`).\n *\n * Builds a card for every candidate symbol (src/analyzers/cards) and applies the Tier-0 rules\n * from this analyzer's extraction heuristics. Reads the graph only: no model calls, no writes.\n *\n * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules\n */\n async classify(projectRoot: string, options: ClassifyOptions & AnalyzerCodebaseOptions = {}): Promise<ClassifyReport> {\n const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot;\n const rules = tier0RulesFrom(this.mapper.getHeuristics());\n if (rules.length === 0) {\n throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER, [\n \"Tier-0 rules are heuristics with a parameters.tier0 block in the analyzer's extraction-heuristics.json\",\n ]);\n }\n\n if (options.labels?.length) {\n const known = new Set([...this.mapper.getNodeLabels(), ...defaultLabels(rules), ...Object.keys(CARD_FIELDS)]);\n const unknown = options.labels.filter((l) => !known.has(l));\n if (unknown.length) {\n throw new CLIError(`Unknown label${unknown.length > 1 ? \"s\" : \"\"}: ${unknown.join(\", \")}`, ErrorCategory.USER, [\n `Labels are case-sensitive; known labels: ${[...known].sort().join(\", \")}`,\n ]);\n }\n }\n\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]);\n }\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\"CBM analyzer not installed\", ErrorCategory.NOT_FOUND, [\n \"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\",\n ]);\n }\n\n const client = new StdioClient();\n try {\n client.spawn(detection.binary_path);\n await client.initialize({ name: \"dr-cli\", version: await getCliVersion() });\n const tools = new CbmTools(client);\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n return await classifyTier0(cbmCardGraph(tools, projectName), rules, this.name, {\n sourceRoot: effectiveCodebaseRoot,\n ...options,\n });\n } finally {\n client.close();\n }\n }\n\n /**\n * Verify that graph-discovered routes align with model endpoints\n *\n * Queries the CBM graph for Route nodes, shapes them via the Route mapping,\n * and delegates comparison to VerifyEngine. Entirely read-only.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Verification options including codebase root\n * @returns Comprehensive verification report\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async verify(\n projectRoot: string,\n options: VerifyOptions & AnalyzerCodebaseOptions\n ): Promise<VerifyReport> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Get the Route mapping BEFORE initializing client to validate it exists\n const routeMapping = this.mapper.getNodeMapping(\"Route\");\n if (!routeMapping) {\n throw new CLIError(\n \"Route mapping not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name for search_graph (requires name, not absolute path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Search for Route nodes using the CBM label from the mapping\n const allNodes = await this.fetchRoutes(tools, projectName, routeMapping.cbm_label);\n // Keep only nodes whose qualified_name follows the __route__METHOD__/path\n // convention. Nodes that don't match are graph artifacts (documentation\n // strings, base URLs, etc.) that were incidentally labelled Route by the\n // indexer \u2014 not actual HTTP routes.\n const nodes = allNodes\n .filter((n) => /^__route__[A-Z]+__\\//.test(String(n.qualified_name ?? \"\")))\n .filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? \"\")));\n\n // Shape routes via mapping; drop unattributed nodes (same filter as endpoints())\n const routes = nodes\n .map((node) => this.shapeRoute(node))\n .filter((r) => r.source_file);\n\n // Delegate to VerifyEngine\n const engine = new VerifyEngine();\n const indexMeta = status.index_meta;\n const analyzerName = this.name;\n const report = await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);\n\n return report;\n } finally {\n client.close();\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * CBM (Codebase Memory) Analyzer Backend\n *\n * Implements the AnalyzerBackend interface for the codebase-memory-mcp analyzer.\n * Handles detection, indexing, and querying of code-based metadata using the CBM MCP server.\n *\n * Key responsibilities:\n * - Detect CBM binary availability and MCP registration\n * - Manage project indexing with freshness tracking\n * - Query for endpoints and other code artifacts\n * - Transform CBM nodes to DR elements using mapping templates\n */\n\nimport { spawnSync } from \"child_process\";\nimport * as path from \"path\";\nimport { readFile, writeFile } from \"fs/promises\";\nimport { CLIError, ErrorCategory, handleWarning } from \"../utils/errors.js\";\nimport type { AnalyzerBackend, AnalyzerCodebaseOptions } from \"./base-analyzer.js\";\nimport type {\n AnalyzerStatus,\n DetectionResult,\n EndpointCandidate,\n IndexResult,\n IndexMeta,\n AnalyzerNodeMapping,\n AnalyzerHeuristic,\n HttpMethod,\n ServiceCandidate,\n DatastoreCandidate,\n CallGraphNode,\n VerifyOptions,\n VerifyReport,\n} from \"./types.js\";\nimport { VALID_HTTP_METHODS } from \"./types.js\";\nimport { StdioClient } from \"./stdio-client.js\";\nimport { readIndexMeta, writeIndexMeta } from \"./session-state.js\";\nimport type { MappingLoader } from \"./mapping-loader.js\";\nimport { getCliVersion } from \"./version.js\";\nimport { StagingAreaManager } from \"../core/staging-area.js\";\nimport { DR_OWN_DIRS } from \"./code-changes.js\";\nimport { VerifyEngine } from \"./verify-engine.js\";\nimport { clampDepth, shapeCallGraphNode, determineEdgeType } from \"./call-graph-utils.js\";\nimport { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from \"./heuristic-utils.js\";\nimport { toKebabCase } from \"../utils/id-generator.js\";\nimport {\n CbmTools,\n CbmToolError,\n ROUTE_HANDLERS_QUERY,\n attachRouteHandlers,\n nodeColumns,\n nodesFromQuery,\n relativeToRoot,\n routeHandlersFromQuery,\n} from \"./cbm-responses.js\";\nimport type { CbmGraphNode } from \"./cbm-responses.js\";\nimport { cbmCardGraph } from \"./cards/graph.js\";\nimport { classifyTier0, defaultLabels, type ClassifyOptions, type ClassifyReport } from \"./cards/classify.js\";\nimport { CARD_FIELDS } from \"./cards/build.js\";\nimport { tier0RulesFrom } from \"./tier0-rules.js\";\nimport { collectExtraction, extractionConfigFrom, type ExtractOptions, type ExtractionConfig, type ExtractionGraph } from \"./cards/extract.js\";\n\n/** Per-call timeout for extraction's graph reads (the stdio client's default is 30s). */\nconst EXTRACT_CALL_TIMEOUT_MS = 120_000;\n\n// Source-code file extensions that CBM sometimes mislabels as Route nodes when it\n// encounters string literals that look like paths (e.g. \"/src/app.ts\").\n// Only source-code extensions are listed \u2014 .json, .xml, .csv, .html etc. are\n// legitimate HTTP route suffixes and must NOT be filtered out.\nexport const SOURCE_CODE_EXT_RE = /\\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|java|rs|php|cs|cpp|c|h|kt|swift|sh|bash)$/i;\n\n/**\n * Returns true when a qualified_name looks like a real HTTP route rather than\n * a source-file path that the CBM indexer mislabelled as a Route node.\n */\nfunction isLikelyHttpRoute(qualifiedName: string): boolean {\n const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, \"\");\n // Reject source-code file paths, internal DR paths, and spec artifact paths\n // (e.g. /schemas/motivation/goal from test fixtures or openapi schema refs)\n return !SOURCE_CODE_EXT_RE.test(httpPath)\n && !httpPath.startsWith(\"/.dr/\")\n && !httpPath.startsWith(\"/schemas/\");\n}\n\n/**\n * Infer HTTP method from a function name using naming conventions.\n * Returns \"GET\" by default for ambiguous names.\n */\nfunction inferHttpMethodFromFunctionName(name: string): HttpMethod {\n const lower = name.toLowerCase();\n if (/^(create|add|post|submit|send)/.test(lower)) return \"POST\";\n if (/^(update|put|save|replace|set)/.test(lower)) return \"PUT\";\n if (/^(patch|merge)/.test(lower)) return \"PATCH\";\n if (/^(delete|remove|destroy)/.test(lower)) return \"DELETE\";\n return \"GET\";\n}\n\n/**\n * Infer an HTTP path from a function name by stripping the verb prefix and\n * converting the remainder to kebab-case.\n */\nfunction inferHttpPathFromFunctionName(name: string): string {\n const stripped = name.replace(/^(fetch|get|list|create|add|update|put|delete|remove|patch|merge|load|save|replace|post|submit|send|destroy)/i, \"\");\n const segment = toKebabCase(stripped || name);\n return segment ? `/${segment}` : \"/\";\n}\n\n/** Element types whose CBM labels can yield service candidates in services(). */\nconst SERVICE_SOURCE_TYPES = new Set([\"applicationfunction\", \"applicationcomponent\", \"applicationservice\"]);\n\n/**\n * CBM Analyzer implementation\n */\nexport class CbmAnalyzer implements AnalyzerBackend {\n private mapper: MappingLoader;\n private testCodePatternWarned = false;\n private warnedHeuristics = new Set<string>();\n\n /**\n * Test code detection when the mapping has no test_code_exclusion rule: the same test-file\n * pattern as the mapping's rule and its tier0_test_file rule.\n */\n private static readonly DEFAULT_TEST_PATTERNS = [\n /((^|\\/)(tests?|__tests__|__fixtures__|fixtures?)\\/|(^|\\/)test_[^/]*\\.py$|(^|\\/)conftest\\.py$|_test\\.(go|py)$|\\.(test|spec)\\.[cm]?[jt]sx?$|Tests?\\.(java|kt)$)/,\n ];\n\n constructor(mapper: MappingLoader) {\n this.mapper = mapper;\n }\n\n /**\n * Get the analyzer name\n */\n get name(): string {\n return this.mapper.getAnalyzerName();\n }\n\n /**\n * Get the analyzer display name\n */\n get displayName(): string {\n const metadata = this.mapper.getAnalyzerMetadata();\n return metadata?.display_name || this.mapper.getAnalyzerName();\n }\n\n /**\n * Check if the MCP server is registered in .mcp.json\n *\n * Reads .mcp.json from the project root and checks if the analyzer's MCP server\n * is registered there. This is extracted into a separate method for easier testing.\n *\n * @param projectRoot Absolute path to the project root\n * @returns True if the MCP server is registered, false otherwise\n * @throws CLIError if .mcp.json exists but is unreadable or malformed\n */\n async checkMcpRegistration(projectRoot: string): Promise<boolean> {\n try {\n const mcpJsonPath = path.join(projectRoot, \".mcp.json\");\n const mcpContent = await readFile(mcpJsonPath, \"utf-8\");\n const mcpConfig = JSON.parse(mcpContent);\n const metadata = this.mapper.getAnalyzerMetadata();\n const mcpServerName = metadata?.mcp_server_name ?? \"codebase-memory-mcp\";\n // Check if the analyzer's MCP server is registered in .mcp.json\n return (mcpConfig.mcpServers && mcpServerName in mcpConfig.mcpServers) || false;\n } catch (error) {\n // ENOENT (file not found) is expected - MCP registration is optional\n const err = error as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\") {\n return false;\n }\n\n // Other errors indicate actionable problems\n if (err.code === \"EACCES\") {\n throw new CLIError(\n \"Permission denied reading .mcp.json\",\n ErrorCategory.SYSTEM,\n [\n `Cannot read ${path.join(projectRoot, \".mcp.json\")}`,\n \"Check file permissions\",\n ]\n );\n }\n\n // SyntaxError during JSON.parse or other read/parse errors\n throw new CLIError(\n \"Invalid .mcp.json format\",\n ErrorCategory.VALIDATION,\n [\n `Check that ${path.join(projectRoot, \".mcp.json\")} contains valid JSON`,\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n ]\n );\n }\n }\n\n /**\n * Detect if the analyzer is installed and functional\n *\n * Checks for the presence of the analyzer binary, reads .mcp.json for registration info,\n * and validates the required tool contract by calling initialize and verifying required_tools.\n *\n * @param projectRoot Optional absolute path to project root (defaults to cwd)\n * @returns Detection result with binary path, version, and MCP registration status\n */\n async detect(projectRoot?: string): Promise<DetectionResult> {\n // Get binary names from the analyzer metadata\n const metadata = this.mapper.getAnalyzerMetadata();\n const binaryNames = (metadata?.binary_names as string[] | undefined) ?? [\n \"codebase-memory-mcp\",\n ];\n\n // Check if .mcp.json exists at project root for registration status\n // Use provided projectRoot or fallback to cwd\n let mcpRegistered = false;\n try {\n mcpRegistered = await this.checkMcpRegistration(projectRoot || process.cwd());\n } catch (error) {\n // CLIError from checkMcpRegistration indicates actionable problems (EACCES, syntax errors)\n // Surface these to the user via handleWarning(), but continue with detection\n if (error instanceof CLIError) {\n handleWarning(error.message, error.suggestions);\n } else {\n // Unexpected error - still continue with detection but warn\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Unexpected error checking MCP registration\", [errorMsg]);\n }\n mcpRegistered = false;\n }\n\n for (const binaryName of binaryNames) {\n // Check if binary is available using 'which'\n const whichResult = spawnSync(\"which\", [binaryName], {\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (whichResult.status !== 0) {\n continue;\n }\n\n const binaryPath = whichResult.stdout.trim();\n\n if (!binaryPath) {\n continue;\n }\n\n // Try to initialize the analyzer to validate the tool contract\n const client = new StdioClient();\n try {\n client.spawn(binaryPath);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Verify required_tools from mapping are available on the server\n let contractOk = true;\n const requiredTools = (metadata?.supported_tool_contract as Record<string, unknown> | undefined)?.required_tools as string[] | undefined ?? [];\n\n if (requiredTools.length > 0) {\n try {\n // Verify all required tools are listed in supported tools\n // The MCP spec requires tools to be registered at initialization\n const response = await client.callTool(\"tools/list\", {}) as { tools?: Array<{ name: string }> };\n\n const availableTools = new Set(\n (response.tools ?? []).map((tool) => tool.name)\n );\n\n for (const tool of requiredTools) {\n if (!availableTools.has(tool)) {\n contractOk = false;\n handleWarning(\n `Required tool '${tool}' not available in ${binaryName}`,\n [\"Ensure the analyzer server exports all required tools\"]\n );\n }\n }\n } catch (error) {\n // If tool listing fails, treat as verification failure\n contractOk = false;\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to verify required tools for ${binaryName}: ${errorMsg}`,\n [\"Ensure all required tools are available in the analyzer server\"]\n );\n }\n }\n\n // Successfully initialized and verified contract\n return {\n installed: true,\n binary_path: binaryPath,\n version: undefined, // Version could be extracted from --version if needed\n mcp_registered: mcpRegistered,\n contract_ok: contractOk,\n };\n } catch (error) {\n // Initialize failed - log diagnostic info and continue to next binary\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n handleWarning(\n `Binary ${binaryName} failed initialization: ${errorMessage}`,\n [\"Check that the binary is properly installed and functional\"]\n );\n continue;\n } finally {\n // Always close the client to prevent orphan processes\n try {\n client.close();\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to close MCP client for ${binaryName}: ${errorMessage}`,\n [\n \"The MCP server process may still be running\",\n \"Try restarting your terminal or manually killing the process\",\n ]\n );\n }\n }\n }\n\n // No binary found or functional\n return {\n installed: false,\n };\n }\n\n /**\n * Get the current status of the analyzer for a project\n *\n * Checks if the project is indexed and whether the index is fresh relative to git HEAD.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Current status including detection, index state, and freshness\n */\n async status(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<AnalyzerStatus> {\n const detected = await this.detect(projectRoot);\n\n // Resolve the effective codebase root for git operations\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Read index metadata if it exists (keyed to model root)\n const analyzerName = this.mapper.getAnalyzerName();\n const indexMeta = await readIndexMeta(projectRoot, analyzerName);\n\n // Check if indexed\n const indexed = indexMeta !== null;\n\n if (!indexed) {\n return {\n detected,\n indexed: false,\n fresh: false,\n };\n }\n\n // Check freshness: compare stored git HEAD to current HEAD\n // Run git in the effective codebase root, not the model root\n let fresh = false;\n try {\n const currentHeadResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: effectiveCodebaseRoot,\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (currentHeadResult.status === 0) {\n const currentHead = currentHeadResult.stdout.trim();\n fresh = currentHead === indexMeta.git_head;\n } else {\n // Git command failed - log diagnostic info but don't fail if codebaseRoot was provided\n const gitError = currentHeadResult.stderr?.trim() || \"git returned non-zero status\";\n if (options?.codebaseRoot) {\n handleWarning(\"Failed to check git freshness in codebase root\", [\n `Codebase root: ${effectiveCodebaseRoot}`,\n `Git error: ${gitError}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n } else {\n handleWarning(\"Failed to check git freshness\", [\n `Git error: ${gitError}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n }\n fresh = false;\n }\n } catch (error) {\n // Git spawn failed completely - log diagnostic info\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Failed to spawn git process\", [\n `Error: ${errorMsg}`,\n \"Index freshness cannot be determined - treating as stale\",\n ]);\n fresh = false;\n }\n\n return {\n detected,\n indexed: true,\n index_meta: indexMeta,\n fresh,\n last_indexed: indexMeta.timestamp,\n };\n }\n\n /**\n * Index a project or update the index\n *\n * Creates a project entry in the CBM backend if it doesn't already exist,\n * indexes the repository, and writes freshness metadata.\n *\n * Skips indexing if the index is fresh (git HEAD matches) unless --force is set.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional configuration including force flag and codebase root\n * @returns Index result with counts and metadata\n */\n async index(\n projectRoot: string,\n options?: { force?: boolean; timeoutMs?: number } & AnalyzerCodebaseOptions\n ): Promise<IndexResult> {\n // Resolve the effective codebase root for git and indexing operations\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Get current status (passing codebaseRoot option)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n\n // Skip if fresh and not forced\n if (status.indexed && status.fresh && !options?.force) {\n handleWarning(\"Index is fresh. Use --force to reindex.\", [\n `Last indexed: ${status.last_indexed}`,\n ]);\n\n // Return the existing metadata\n const meta = status.index_meta;\n return {\n node_count: meta.node_count,\n edge_count: meta.edge_count,\n git_head: meta.git_head,\n timestamp: meta.timestamp,\n };\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\n \"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\",\n \"Ensure the binary is in your PATH\",\n ]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Check if project is already indexed using list_projects\n // This validates the acceptance criterion and prevents duplicate project creation\n const projects = await tools.listProjects();\n const projectExists = projects.some((p) => p.root_path === effectiveCodebaseRoot);\n\n // Prevent duplicate CBM entries if project exists and index is fresh.\n // Per FR-3.3, --force is only needed to override a fresh index, not for stale re-indexing.\n // list_projects check prevents duplicate entries; freshness is already checked at line 313.\n if (projectExists && status.fresh && !options?.force) {\n handleWarning(\n \"Project already indexed in CBM backend with fresh index.\",\n [\"Use --force to re-index the project\"]\n );\n\n // Return actual index metadata instead of fabricated zeros\n // Guard against index_meta being undefined (can happen if metadata is corrupted or deleted)\n if (!status.index_meta) {\n throw new CLIError(\n \"Project exists in CBM backend but local metadata is missing\",\n ErrorCategory.VALIDATION,\n [\n \"Use --force to re-index and regenerate metadata\",\n \"Or manually delete the project from CBM backend and re-index\"\n ]\n );\n }\n\n const meta = status.index_meta;\n return {\n node_count: meta.node_count,\n edge_count: meta.edge_count,\n git_head: meta.git_head,\n timestamp: meta.timestamp,\n };\n }\n\n // Get current git HEAD from the effective codebase root\n // This must happen before index_repository to avoid inconsistent state\n const headResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: effectiveCodebaseRoot,\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (headResult.status !== 0) {\n throw new CLIError(\n \"Failed to get git HEAD\",\n ErrorCategory.SYSTEM,\n [\n \"Ensure the codebase root is a git repository\",\n `Codebase root: ${effectiveCodebaseRoot}`,\n \"Verify git is installed and functional\",\n `Error: ${headResult.stderr?.trim() || \"unknown error\"}`,\n ]\n );\n }\n\n const gitHead = headResult.stdout.trim();\n\n // Read and log the active changeset for consistency tracking (v1: logging only, no branching)\n try {\n const stagingManager = new StagingAreaManager(projectRoot);\n const activeChangesetId = await stagingManager.getActiveId();\n if (activeChangesetId) {\n console.log(`Indexing with active changeset: ${activeChangesetId}`);\n }\n } catch (error) {\n // If reading active changeset fails, log a warning but continue indexing\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Failed to read active changeset\",\n [`Error: ${errorMsg}`, \"Continuing with indexing\"]\n );\n }\n\n // DR's own folders (the model, changesets, analyzer state) are not code; keep them out.\n try {\n const ignore = await ensureCbmIgnore(effectiveCodebaseRoot, projectRoot);\n if (ignore.added.length > 0) {\n console.log(`Added ${ignore.added.join(\", \")} to ${ignore.path} so the index leaves DR's own files out`);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Could not update .cbmignore; DR's own files may be indexed\", [`Error: ${errorMsg}`]);\n }\n\n // The index time is when the run started reading files: an edit made during the run is newer.\n const timestamp = new Date().toISOString();\n // Index the repository using the effective codebase root\n const indexResponse = await tools.indexRepository(\n effectiveCodebaseRoot,\n options?.timeoutMs ? { timeout: options.timeoutMs } : undefined\n );\n\n const nodeCount = typeof indexResponse.nodes === \"number\" ? indexResponse.nodes : 0;\n const edgeCount = typeof indexResponse.edges === \"number\" ? indexResponse.edges : 0;\n\n // Warn if counts were not numbers and defaulted to zero\n if (typeof indexResponse.nodes !== \"number\") {\n handleWarning(\n \"Invalid node count from index_repository\",\n [\n `Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,\n \"Defaulting to 0 - index may be incomplete or corrupted\",\n ]\n );\n }\n\n if (typeof indexResponse.edges !== \"number\") {\n handleWarning(\n \"Invalid edge count from index_repository\",\n [\n `Expected number, got ${typeof indexResponse.edges}: ${String(indexResponse.edges)}`,\n \"Defaulting to 0 - index may be incomplete or corrupted\",\n ]\n );\n }\n\n // Write index metadata\n const meta: IndexMeta = {\n git_head: gitHead,\n timestamp,\n node_count: nodeCount,\n edge_count: edgeCount,\n };\n\n const analyzerName = this.mapper.getAnalyzerName();\n await writeIndexMeta(meta, projectRoot, analyzerName);\n\n return {\n node_count: nodeCount,\n edge_count: edgeCount,\n git_head: gitHead,\n timestamp,\n };\n } finally {\n client.close();\n }\n }\n\n /**\n * Fetch Route nodes with their HANDLES-edge handlers attached.\n *\n * CBM Route nodes carry only `{ method, source }` and often no file (every fastapi route,\n * for example); the handler function, its symbol, lines and file come from HANDLES edges.\n * If the handler query fails the routes are returned as they are.\n *\n * @private\n */\n private async fetchRoutes(tools: CbmTools, projectName: string, label: string): Promise<CbmGraphNode[]> {\n const routes = await tools.searchGraph({ label, project: projectName });\n try {\n const handlers = routeHandlersFromQuery(await tools.queryGraph(ROUTE_HANDLERS_QUERY, projectName));\n return attachRouteHandlers(routes, handlers);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Could not read route handlers (HANDLES edges); routes lack handler details\", [\n `Error: ${errorMsg}`,\n ]);\n return routes;\n }\n }\n\n /**\n * Resolve the internal project name used by codebase-memory-mcp for a given root path.\n *\n * list_projects returns { name, root_path } per entry. search_graph and other\n * query tools require the internal `name`, not the absolute `root_path`.\n * Falls back to the absolute path with a warning if the project is not found.\n */\n private async resolveProjectName(\n client: StdioClient | CbmTools,\n codebaseRoot: string\n ): Promise<string> {\n const tools = client instanceof CbmTools ? client : new CbmTools(client);\n const projects = await tools.listProjects();\n const match = projects.find((p) => p.root_path === codebaseRoot);\n if (!match) {\n handleWarning(\n \"Project not found in CBM backend; using absolute path as project identifier\",\n [\n `Codebase root: ${codebaseRoot}`,\n \"Run `dr analyzer index` to ensure the project is indexed\",\n ]\n );\n return codebaseRoot;\n }\n return match.name;\n }\n\n /**\n * Query for API endpoints/routes in the indexed project\n *\n * Searches the indexed graph for Route nodes, applies field mappings from\n * the analyzer's mapping, and filters based on test code exclusion rules.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of endpoint candidates with confidence and source info\n * @throws CLIError if the project is not indexed\n */\n async endpoints(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<EndpointCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Get the Route mapping BEFORE initializing client to validate it exists\n const routeMapping = this.mapper.getNodeMapping(\"Route\");\n if (!routeMapping) {\n throw new CLIError(\n \"Route mapping not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name for search_graph (requires name, not absolute path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Search for Route nodes using the CBM label from the mapping\n const allNodes = await this.fetchRoutes(tools, projectName, routeMapping.cbm_label);\n // Keep only nodes whose qualified_name follows the __route__METHOD__/path\n // convention. Nodes that don't match are graph artifacts (documentation\n // strings, base URLs, etc.) that were incidentally labelled Route by the\n // indexer \u2014 not actual HTTP routes.\n const nodes = allNodes\n .filter((n) => /^__route__[A-Z]+__\\//.test(String(n.qualified_name ?? \"\")))\n .filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? \"\")));\n\n // Transform nodes to endpoint candidates\n const candidates: EndpointCandidate[] = [];\n\n for (const node of nodes) {\n const candidate = await this.transformNodeToEndpoint(\n node,\n routeMapping,\n effectiveCodebaseRoot\n );\n\n // Skip unattributable candidates \u2014 empty source_file means the graph\n // found a path string in non-route code (test assertions, fixture data)\n if (!candidate.source_file) {\n continue;\n }\n // Apply test code exclusion filter\n if (!this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n\n // ISSUE-003: Client-side API consumer detection.\n // When no server-side Route nodes are found (e.g. React/frontend apps using fetch/axios),\n // fall back to querying Function nodes in API-client files whose names follow HTTP verb\n // conventions. These are low-confidence candidates \u2014 the user confirms and adds them.\n if (candidates.length === 0) {\n try {\n const clientQuery = `\n MATCH (n:Function)\n WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'\n OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'\n OR n.file_path CONTAINS 'service' OR n.file_path CONTAINS 'http'\n OR n.file_path CONTAINS 'Http')\n AND (n.name STARTS WITH 'fetch' OR n.name STARTS WITH 'get'\n OR n.name STARTS WITH 'post' OR n.name STARTS WITH 'put'\n OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'\n OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'\n OR n.name STARTS WITH 'list')\n RETURN ${nodeColumns(\"n\")}\n `;\n\n const clientNodes = nodesFromQuery(await tools.queryGraph(clientQuery, projectName));\n for (const node of clientNodes) {\n if (!node.file_path) continue;\n const candidate = this.shapeClientSideEndpoint(node, effectiveCodebaseRoot);\n if (candidate && !this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n } catch (error) {\n // query_graph not available \u2014 skip the client-side fallback\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Client-side endpoint fallback skipped: query_graph failed\", [`Error: ${errorMsg}`]);\n }\n }\n\n this.warnOnDuplicateIdFragments(\n candidates,\n (c) => c.suggested_id_fragment,\n (c) => c.source_symbol || c.source_file || \"(unknown)\"\n );\n return candidates;\n } finally {\n client.close();\n }\n }\n\n /**\n * Execute a raw Cypher query against the analyzer's graph\n *\n * Passes the raw Cypher string directly to the query_graph MCP tool.\n * The query_graph tool is optional (not in required_tools contract).\n * If the tool call fails, surfaces a clear error message.\n *\n * @param projectRoot Absolute path to the project root\n * @param rawQuery Raw Cypher query string\n * @param options Optional codebase configuration\n * @returns Raw result from query_graph tool\n * @throws CLIError if project not indexed or query_graph tool unavailable\n */\n async query(projectRoot: string, rawQuery: string, options?: AnalyzerCodebaseOptions): Promise<unknown> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed (same pre-flight checks as endpoints)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name \u2014 query_graph requires the CBM name, not the absolute path\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Call query_graph tool; all result pages are merged into one { columns, rows }\n try {\n const result = await tools.queryGraph(rawQuery, projectName);\n return { ...result, total: result.rows.length };\n } catch (error) {\n // CBM rejected the query (syntax error, unsupported clause): surface its message\n if (error instanceof CbmToolError) {\n throw new CLIError(`Query failed: ${error.detail}`, ErrorCategory.USER, error.suggestions);\n }\n\n // Check if the error is due to the tool not being available\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes(\"query_graph\") || errorMessage.includes(\"Unknown method\")) {\n throw new CLIError(\n \"query_graph not supported by this analyzer\",\n ErrorCategory.USER,\n [\n \"The query_graph tool is optional in the CBM analyzer contract\",\n \"Ensure your codebase-memory-mcp version supports raw graph queries\",\n \"Contact the analyzer maintainers if you need this feature\",\n ]\n );\n }\n\n // Re-throw other errors\n throw error;\n }\n } finally {\n client.close();\n }\n }\n\n private warnOnDuplicateIdFragments<T>(\n items: T[],\n getKey: (item: T) => string | undefined,\n getLabel: (item: T) => string\n ): void {\n const seen = new Map<string, string>();\n for (const item of items) {\n const key = getKey(item);\n if (!key) continue;\n const label = getLabel(item);\n if (seen.has(key)) {\n handleWarning(\n `Duplicate suggested ID \"${key}\" \u2014 multiple symbols map to the same element ID`,\n [\n `First: ${seen.get(key)!}`,\n `Conflict: ${label}`,\n `Rename one or use \"dr add\" with a custom name`,\n ]\n );\n } else {\n seen.set(key, label);\n }\n }\n }\n\n /**\n * Transform a CBM graph node to an endpoint candidate\n *\n * Maps CBM Route node properties to DR EndpointCandidate contract:\n * - suggested_layer: always \"api\" for endpoints\n * - suggested_element_type: always \"operation\" for routes\n * - suggested_name: from node properties or id (kebab-case)\n * - http_method: from properties.method or defaults to \"GET\"\n * - http_path: from properties.path or defaults to \"/\"\n * - handler_qualified_name: from properties.handler_name or empty string\n * - source_symbol: from properties.symbol or empty string\n * - source_start_line: from properties.start_line or 0\n * - source_end_line: from properties.end_line or 0\n *\n * Downgrades confidence if required fields are missing.\n *\n * @private\n */\n private async transformNodeToEndpoint(\n node: CbmGraphNode,\n mapping: AnalyzerNodeMapping,\n codebaseRoot: string\n ): Promise<EndpointCandidate> {\n const properties = node.properties ?? {};\n // Node identifier for display \u2014 actual codebase-memory-mcp nodes use 'name', not 'id'\n const nodeId = String(node.name ?? node.id ?? \"(unknown)\");\n\n // Extract base fields\n let confidence = mapping.confidence as \"high\" | \"medium\" | \"low\";\n\n // Suggested name in kebab-case: properties.name \u2192 node.name \u2192 node.id\n const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? \"\"));\n\n // Suggested ID fragment (same as name for endpoints)\n const suggestedIdFragment = suggestedName;\n\n // Required fields for dr add api operation\n // Extract HTTP method: properties first, then parse qualified_name pattern __route__METHOD__/path\n let rawMethod: string;\n if (typeof properties.method === \"string\") {\n rawMethod = properties.method.toUpperCase();\n } else {\n const qn = String(node.qualified_name ?? \"\");\n const methodMatch = qn.match(/^__route__([A-Z]+)__/);\n rawMethod = methodMatch?.[1] ?? \"GET\";\n if (rawMethod === \"ANY\") rawMethod = \"GET\";\n }\n\n // Validate against valid HTTP methods using the constant\n const validMethods = new Set(VALID_HTTP_METHODS);\n const isValidMethod = validMethods.has(rawMethod as HttpMethod);\n const httpMethod: HttpMethod = isValidMethod\n ? (rawMethod as HttpMethod)\n : \"GET\";\n\n if (!isValidMethod) {\n handleWarning(\n `Node ${nodeId}: Invalid HTTP method`,\n [\n `Method '${rawMethod}' is not a valid HTTP method`,\n `Valid methods: ${Array.from(validMethods).join(\", \")}`,\n \"Defaulting to GET - endpoint documentation may be inaccurate\",\n ]\n );\n }\n\n // Extract HTTP path: properties first, then node.name (which IS the URL path for flat nodes)\n let httpPath: string;\n if (typeof properties.path === \"string\") {\n httpPath = properties.path;\n } else if (typeof node.name === \"string\" && node.name.startsWith(\"/\")) {\n httpPath = node.name;\n } else {\n handleWarning(\n `Node ${nodeId}: Missing or invalid HTTP path`,\n [\n `Expected string, got ${typeof properties.path}`,\n \"Defaulting to / - endpoint may be incomplete\",\n ]\n );\n httpPath = \"/\";\n }\n\n // Handler information \u2014 try multiple property names because CBM indexers vary.\n // Do NOT fall back to properties.qualified_name \u2014 that is the route node's own QN.\n const handlerQualifiedName = String(\n properties.handler_name ??\n properties.handler_qualified_name ??\n \"\"\n );\n const sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? \"\");\n const sourceStartLine = Number(properties.start_line ?? 0);\n const sourceEndLine = Number(properties.end_line ?? 0);\n\n // Downgrade confidence if handler information is missing\n if (!handlerQualifiedName || !sourceSymbol) {\n confidence = capConfidence(confidence);\n }\n\n // Extract source file \u2014 check both node-level and properties-level fields\n let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? \"\");\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path for ${nodeId}`,\n [\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // sourceFile keeps its original absolute value\n }\n }\n\n return {\n source_file: sourceFile,\n confidence,\n suggested_layer: \"api\",\n suggested_element_type: \"operation\",\n suggested_name: suggestedName,\n suggested_id_fragment: suggestedIdFragment,\n http_method: httpMethod,\n http_path: httpPath,\n handler_qualified_name: handlerQualifiedName,\n source_symbol: sourceSymbol,\n source_start_line: sourceStartLine,\n source_end_line: sourceEndLine,\n source_reference: {\n provenance: \"extracted\",\n locations: sourceFile\n ? [\n {\n file: sourceFile,\n symbol: sourceSymbol || undefined,\n },\n ]\n : undefined,\n },\n };\n }\n\n\n /**\n * Check if a candidate (endpoint, service, or datastore) is in test code\n *\n * Applies the test_code_exclusion filtering rule from the analyzer mapping.\n * If the configured regex pattern is invalid, warns the user once and falls back to defaults.\n * Uses a one-shot flag to ensure the warning is only emitted once even if called in a loop.\n *\n * Accepts any object with a source_file property (structural typing), allowing it to work\n * with EndpointCandidate, ServiceCandidate, DatastoreCandidate, and temporary objects.\n *\n * @private\n */\n\n /**\n * Build a low-confidence EndpointCandidate from a client-side API function node.\n * Used as a fallback when no server-side Route nodes are found (ISSUE-003).\n */\n private shapeClientSideEndpoint(node: CbmGraphNode, codebaseRoot: string): EndpointCandidate | null {\n const name = String(node.name ?? node.id ?? \"\");\n if (!name) return null;\n\n let sourceFile = String(node.file_path ?? \"\");\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch {\n // keep absolute path on failure\n }\n }\n if (!sourceFile) return null;\n\n const httpMethod = inferHttpMethodFromFunctionName(name);\n const httpPath = inferHttpPathFromFunctionName(name);\n const suggestedName = toKebabCase(name);\n\n return {\n source_file: sourceFile,\n confidence: \"low\",\n suggested_layer: \"api\",\n suggested_element_type: \"operation\",\n suggested_name: suggestedName,\n suggested_id_fragment: suggestedName,\n http_method: httpMethod,\n http_path: httpPath,\n handler_qualified_name: name,\n source_symbol: name,\n source_start_line: Number(node.properties?.start_line ?? 0),\n source_end_line: Number(node.properties?.end_line ?? 0),\n source_reference: {\n provenance: \"inferred\",\n locations: [{ file: sourceFile, symbol: name }],\n },\n };\n }\n\n private isTestCode(candidate: { source_file: string }): boolean {\n // Get filtering rules from the analyzer mapping\n const filteringRules = this.mapper.getFilteringRules();\n\n // Find the test_code_exclusion rule\n const testCodeRule = filteringRules.find(\n (rule) => rule.name === \"test_code_exclusion\" && rule.enabled\n );\n\n // If no test_code_exclusion rule found, apply default patterns as fallback\n if (!testCodeRule || !testCodeRule.pattern) {\n return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) =>\n pattern.test(candidate.source_file)\n );\n }\n\n // Apply the pattern from the filtering rule\n try {\n const testCodePattern = new RegExp(testCodeRule.pattern);\n return testCodePattern.test(candidate.source_file);\n } catch (error) {\n // If regex is invalid, warn user once (not on every invocation in the loop)\n // and fall back to default patterns\n if (!this.testCodePatternWarned) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Invalid test_code_exclusion regex pattern in analyzer mapping\",\n [\n `Pattern: ${testCodeRule.pattern}`,\n `Error: ${errorMsg}`,\n \"Falling back to default test code patterns\",\n ]\n );\n this.testCodePatternWarned = true;\n }\n return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) =>\n pattern.test(candidate.source_file)\n );\n }\n }\n\n /**\n * Query for services/components in the indexed project\n *\n * Searches for application-layer nodes using the mapping-loader-driven pattern.\n * For each node, evaluates all promotion_heuristics and records which fire in\n * qualifying_heuristics. Drops candidates with zero qualifying heuristics (unless\n * is_entry_point heuristic fires, adding it to qualifying_heuristics). Caps confidence\n * at \"medium\" and excludes test files.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of service candidates with qualifying heuristics populated\n * @throws CLIError if the project is not indexed\n */\n async services(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<ServiceCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Get application-layer node labels from the mapping (pattern from endpoints())\n const nodeLabels = this.mapper.getNodeLabels();\n const applicationLabels: string[] = [];\n\n // Filter to only application-layer labels by checking their mapping\n for (const label of nodeLabels) {\n const mapping = this.mapper.getNodeMapping(label);\n // Only behavioural and structural types are service sources; data-object labels\n // (Interface, Type, Enum, ...) never become service candidates.\n if (mapping && mapping.dr_layer === \"application\" && SERVICE_SOURCE_TYPES.has(mapping.dr_element_type)) {\n applicationLabels.push(label);\n }\n }\n\n // If no application-layer labels found, warn and return empty list\n if (applicationLabels.length === 0) {\n handleWarning(\n \"No application-layer labels found in analyzer mapping\",\n [\n \"Check that the analyzer is properly configured with application-layer node mappings\",\n \"Services detection requires at least one mapped application-layer node type\",\n \"Returning empty services list\"\n ]\n );\n return [];\n }\n\n const candidates: ServiceCandidate[] = [];\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name once before the label search loop\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Process high-fidelity node types first so the source_file dedup (below) retains\n // class/module level entries over method-level entries from the same file.\n const labelPriority: Record<string, number> = { Class: 0, Module: 1 };\n applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));\n\n // Search for each application-layer label\n for (const label of applicationLabels) {\n const nodes = await tools.searchGraph({ label, project: projectName });\n\n // Get the node mapping for this label\n const nodeMapping = this.mapper.getNodeMapping(label);\n if (!nodeMapping) {\n continue;\n }\n\n // Get promotion heuristics from the mapping\n const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];\n\n // Transform nodes to service candidates\n for (const node of nodes) {\n const candidate = await this.transformNodeToService(\n node,\n nodeMapping,\n effectiveCodebaseRoot,\n promotionHeuristicNames\n );\n\n // Filter out candidates with zero qualifying heuristics (unless is_entry_point is true)\n // and apply test code exclusion filter\n if (candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate)) {\n candidates.push(candidate);\n }\n }\n }\n\n // Deduplicate by source_file \u2014 the model documents at class/file granularity;\n // method-level entries from the same file are noise.\n const seenFiles = new Set<string>();\n const deduped = candidates.filter((c) => {\n if (!c.source_file) return true;\n if (seenFiles.has(c.source_file)) return false;\n seenFiles.add(c.source_file);\n return true;\n });\n this.warnOnDuplicateIdFragments(\n deduped,\n (c) => c.suggested_id_fragment,\n (c) => c.source_symbol || c.source_file || \"(unknown)\"\n );\n return deduped;\n } finally {\n client.close();\n }\n }\n\n /**\n * Transform a CBM graph node to a service candidate\n *\n * Evaluates all promotion heuristics against the node's properties and records\n * which heuristics fire. Drops candidates where no heuristics fire (unless the\n * is_entry_point heuristic fires, which adds it to qualifying_heuristics).\n * Caps confidence at \"medium\".\n *\n * @private\n */\n private async transformNodeToService(\n node: CbmGraphNode,\n mapping: AnalyzerNodeMapping,\n codebaseRoot: string,\n promotionHeuristicNames: string[]\n ): Promise<ServiceCandidate> {\n const properties = node.properties ?? {};\n\n // Suggested name in kebab-case: properties.name \u2192 node.name \u2192 node.id\n const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? \"\"));\n\n // Suggested ID fragment (same as name for services)\n const suggestedIdFragment = suggestedName;\n\n // Extract source file (relative to codebase root)\n let sourceFile = node.file_path ?? \"\";\n if (sourceFile && codebaseRoot) {\n try {\n sourceFile = relativeToRoot(codebaseRoot, sourceFile);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path for ${String(node.name ?? node.id ?? \"(unknown)\")}`,\n [\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // sourceFile keeps its original absolute value\n }\n }\n\n // Extract qualified name: properties first, then node.qualified_name, then node.id\n const qualifiedName = String(properties.qualified_name ?? node.qualified_name ?? node.name ?? node.id ?? \"\");\n\n // Extract fan-in and fan-out metrics\n const fanIn = typeof properties.fan_in === \"number\" ? properties.fan_in : 0;\n const fanOut = typeof properties.fan_out === \"number\" ? properties.fan_out : 0;\n\n // Evaluate all promotion heuristics and collect which ones fire\n const qualifyingHeuristics: string[] = [];\n let isEntryPoint = false;\n\n for (const heuristicName of promotionHeuristicNames) {\n const heuristic = this.mapper.getHeuristic(heuristicName);\n if (!heuristic) {\n // Warn about missing heuristic only once per heuristic name\n if (!this.warnedHeuristics.has(heuristicName)) {\n this.warnedHeuristics.add(heuristicName);\n handleWarning(\n `Promotion heuristic \"${heuristicName}\" not found in analyzer mapping`,\n [\n \"Check the mapping configuration for typos\",\n `Available heuristics: ${getKnownHeuristicNames().join(\", \")}`,\n \"This heuristic will be skipped for service promotion\",\n ]\n );\n }\n continue;\n }\n\n // Evaluate the heuristic against the node\n const fires = this.evaluateHeuristic(heuristic, node, sourceFile);\n if (fires) {\n qualifyingHeuristics.push(heuristicName);\n // Track if is_entry_point heuristic fired\n if (heuristicName === \"is_entry_point\") {\n isEntryPoint = true;\n }\n }\n }\n\n // Drop candidates where zero heuristics fire AND is_entry_point is not true\n const shouldKeep = qualifyingHeuristics.length > 0 || isEntryPoint;\n if (!shouldKeep) {\n // Return a minimal candidate with empty qualifying_heuristics\n // The services() method will filter this out based on qualifying_heuristics.length > 0 check\n const elementType: \"applicationservice\" | \"applicationcomponent\" =\n mapping.dr_element_type_promoted === \"applicationcomponent\"\n ? \"applicationcomponent\"\n : \"applicationservice\";\n return {\n suggested_layer: \"application\",\n suggested_element_type: elementType,\n suggested_id_fragment: suggestedIdFragment,\n suggested_name: suggestedName,\n source_file: sourceFile,\n source_symbol: String(properties.name ?? node.name ?? node.id ?? \"\"),\n qualified_name: qualifiedName,\n qualifying_heuristics: [],\n confidence: \"low\",\n fan_in: fanIn,\n fan_out: fanOut,\n };\n }\n\n // Cap confidence at \"medium\" (from mapping or default to medium)\n const confidence = capConfidence(\n mapping.confidence as \"high\" | \"medium\" | \"low\"\n );\n\n // Ensure element type is one of the valid values\n const elementType: \"applicationservice\" | \"applicationcomponent\" =\n mapping.dr_element_type_promoted === \"applicationcomponent\"\n ? \"applicationcomponent\"\n : \"applicationservice\";\n\n return {\n suggested_layer: \"application\",\n suggested_element_type: elementType,\n suggested_id_fragment: suggestedIdFragment,\n suggested_name: suggestedName,\n source_file: sourceFile,\n source_symbol: String(properties.name ?? node.name ?? node.id ?? \"\"),\n qualified_name: qualifiedName,\n qualifying_heuristics: qualifyingHeuristics,\n confidence,\n fan_in: fanIn,\n fan_out: fanOut,\n };\n }\n\n /**\n * Evaluate a single heuristic against a node\n *\n * Checks if the heuristic's conditions are met based on the node's properties\n * and file path. Unknown heuristics are logged with a warning rather than\n * silently ignored, to support dynamic heuristics from cbm.json.\n *\n * Warnings for unknown heuristics are deduplicated - each heuristic name is warned\n * about only once, even if evaluated against many nodes.\n *\n * @private\n */\n private evaluateHeuristic(\n heuristic: AnalyzerHeuristic,\n node: CbmGraphNode,\n sourceFile: string\n ): boolean {\n const properties = node.properties ?? {};\n\n // Use the heuristic utility function\n const result = evaluateHeuristic(heuristic, properties, sourceFile);\n\n // For unknown heuristics, log a warning (deduplicated by heuristic name)\n if (heuristic.name && !getKnownHeuristicNames().includes(heuristic.name)) {\n // Only warn once per heuristic name\n if (!this.warnedHeuristics.has(heuristic.name)) {\n handleWarning(\n `Unknown heuristic type: ${heuristic.name}`,\n [\n \"The heuristic is defined in cbm.json but not implemented in heuristic-utils.ts\",\n \"This heuristic will be treated as not met (returning false)\",\n \"Add support for this heuristic to evaluateHeuristic() if it should fire\",\n ]\n );\n this.warnedHeuristics.add(heuristic.name);\n }\n }\n\n return result;\n }\n\n /**\n * Match a file path against a glob-like pattern\n *\n * @param filePath The file path to match\n * @param pattern The glob pattern\n * @returns True if the pattern matches, false otherwise\n * @private\n */\n private matchPattern(filePath: string, pattern: string): boolean {\n return matchPattern(filePath, pattern);\n }\n\n /**\n * Query for datastores/databases inferred from code analysis\n *\n * Applies datastore_detection heuristic rules to aggregate signals by file/module.\n * Matches import target names against import_patterns and function/symbol names against\n * naming_indicators from the heuristic parameters. All candidates have confidence \"low\".\n *\n * @param projectRoot Absolute path to the project root\n * @param options Optional codebase configuration\n * @returns Array of datastore candidates aggregated by inferred datastore\n * @throws CLIError if the project is not indexed\n */\n async datastores(projectRoot: string, options?: AnalyzerCodebaseOptions): Promise<DatastoreCandidate[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n // Load the datastore_detection heuristic\n const datastoreHeuristic = this.mapper.getHeuristic(\"datastore_detection\");\n if (!datastoreHeuristic) {\n throw new CLIError(\n \"datastore_detection heuristic not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\n \"The analyzer configuration is missing the datastore_detection heuristic\",\n \"Run `npm run build:spec` to recompile the analyzer artifacts\"\n ]\n );\n }\n\n const importPatterns = datastoreHeuristic.parameters?.patterns ?? [];\n const namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [];\n\n // Map to track signals by datastore name\n const datastoreSignals = new Map<\n string,\n Array<{\n sourceFile: string;\n importPattern?: string;\n functionPatterns: string[];\n }>\n >();\n // Tracks the first import target that produced each normalized datastore name,\n // so we can warn when distinct imports collapse to the same element ID.\n const datastoreFirstSource = new Map<string, string>();\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name once (search_graph and query_graph require name, not path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Try to use query_graph to traverse IMPORTS edges (1 call instead of many)\n // This implements the spec algorithm requirement for IMPORTS edge traversal\n let allNodes: CbmGraphNode[] = [];\n try {\n // Query for all IMPORTS edges and their connected nodes\n const importsQuery = `\n MATCH (n1)-[r:IMPORTS]->(n2)\n RETURN DISTINCT ${nodeColumns(\"n1\")}\n UNION\n MATCH (n1)-[r:IMPORTS]->(n2)\n RETURN DISTINCT ${nodeColumns(\"n2\")}\n `;\n\n allNodes = nodesFromQuery(await tools.queryGraph(importsQuery, projectName));\n } catch (error) {\n // Fallback to search_graph if query_graph is unavailable\n // Warn the user that we're using degraded search semantics\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"query_graph tool failed; falling back to search_graph for datastore detection\",\n [\n `Error: ${errorMsg}`,\n \"Using search_graph will return ALL nodes instead of only IMPORTS-connected nodes\",\n \"Datastore detection results may include false positives or be less accurate\",\n \"Ensure query_graph is supported by your analyzer version for best results\"\n ]\n );\n\n // This provides backward compatibility while still attempting IMPORTS traversal\n const nodeLabels = this.mapper.getNodeLabels();\n for (const label of nodeLabels) {\n allNodes.push(...(await tools.searchGraph({ label, project: projectName })));\n }\n }\n\n // Process all nodes to find datastore signals\n for (const node of allNodes) {\n const filePath = node.file_path ?? \"\";\n\n // For import target detection, check the node's import target name (from label/properties)\n // not the file path. IMPORTS edges connect to target nodes like \"mongodb\", \"pg\"\n const importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? \"\").toLowerCase();\n\n // Check if import target matches datastore-related patterns\n let matchedImportPattern: string | undefined;\n for (const pattern of importPatterns) {\n if (this.matchPattern(importTargetName, pattern)) {\n matchedImportPattern = pattern;\n break;\n }\n }\n\n // Check if node name contains datastore naming indicators\n const nodeName = String(node.properties?.name ?? \"\").toLowerCase();\n const matchedIndicators = namingIndicators.filter((indicator) =>\n nodeName.includes(indicator.toLowerCase())\n );\n\n // Skip if no import pattern matched AND no naming indicators matched\n if (!matchedImportPattern && matchedIndicators.length === 0) {\n continue;\n }\n\n // Extract datastore name from import target name or node properties\n let datastoreName = this.inferDatastoreName(\n filePath,\n node,\n matchedImportPattern,\n importTargetName\n );\n if (!datastoreName) {\n continue;\n }\n\n // Normalize to lowercase and replace special chars\n datastoreName = toKebabCase(datastoreName);\n\n // Get or create entry for this datastore; warn when distinct import targets\n // normalize to the same name (merging is intentional, but the collision is surfaced)\n if (!datastoreSignals.has(datastoreName)) {\n datastoreSignals.set(datastoreName, []);\n datastoreFirstSource.set(datastoreName, importTargetName);\n } else if (datastoreFirstSource.get(datastoreName) !== importTargetName) {\n handleWarning(\n `Multiple imports normalize to datastore name \"${datastoreName}\"`,\n [\n `First: ${datastoreFirstSource.get(datastoreName)!}`,\n `Conflict: ${importTargetName}`,\n `Use \"dr add\" with a custom name to distinguish them`,\n ]\n );\n }\n\n const signals = datastoreSignals.get(datastoreName)!;\n\n // Convert file path to relative\n let relativeFile = filePath;\n if (filePath && effectiveCodebaseRoot) {\n try {\n relativeFile = relativeToRoot(effectiveCodebaseRoot, filePath);\n } catch (error) {\n // Log warning if relative path fails but continue with absolute path\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n `Failed to compute relative path in datastore detection`,\n [\n `File: ${filePath}`,\n `Error: ${errorMsg}`,\n \"Using absolute file path instead of relative\",\n ]\n );\n // relativeFile keeps its original absolute value\n }\n }\n\n // Add signal for this source file\n signals.push({\n sourceFile: relativeFile,\n importPattern: matchedImportPattern,\n functionPatterns: matchedIndicators,\n });\n }\n\n // Transform collected signals into DatastoreCandidate objects\n const candidates: DatastoreCandidate[] = [];\n\n // Convert entries to array to avoid TypeScript MapIterator issues\n const entriesArray = Array.from(datastoreSignals.entries());\n for (const [datastoreName, signals] of entriesArray) {\n // Group by source file and aggregate evidence\n const inferredFromMap = new Map<\n string,\n {\n importPatterns: Set<string>;\n functionPatterns: Set<string>;\n }\n >();\n\n for (const signal of signals) {\n if (!inferredFromMap.has(signal.sourceFile)) {\n inferredFromMap.set(signal.sourceFile, {\n importPatterns: new Set(),\n functionPatterns: new Set(),\n });\n }\n\n const entry = inferredFromMap.get(signal.sourceFile)!;\n if (signal.importPattern) {\n entry.importPatterns.add(signal.importPattern);\n }\n signal.functionPatterns.forEach((fp) => entry.functionPatterns.add(fp));\n }\n\n // Create candidate with aggregated evidence\n let inferredFromMapEntries = Array.from(inferredFromMap.entries());\n\n // Apply test code exclusion filter: remove test files from inferred_from\n inferredFromMapEntries = inferredFromMapEntries.filter(([sourceFile]) => {\n // Create a temporary object with source_file to reuse isTestCode()\n const tempCandidate = {\n source_file: sourceFile,\n };\n return !this.isTestCode(tempCandidate);\n });\n\n // Skip candidates where all sources are test code\n if (inferredFromMapEntries.length === 0) {\n continue;\n }\n\n const inferredFrom = inferredFromMapEntries.map(\n ([sourceFile, evidence]) => ({\n source_file: sourceFile,\n import_pattern: Array.from(evidence.importPatterns).join(\", \") || datastoreName,\n function_patterns: Array.from(evidence.functionPatterns),\n })\n );\n\n candidates.push({\n suggested_layer: \"data-store\",\n suggested_name: datastoreName,\n inferred_from: inferredFrom,\n confidence: \"low\",\n notes: `Inferred from ${inferredFrom.length} source file(s) based on datastore detection heuristics`,\n });\n }\n\n // ISSUE-003: Browser storage detection.\n // localStorage and sessionStorage are global browser APIs \u2014 they don't appear in the\n // IMPORTS graph. Query for Function nodes in files that are commonly associated with\n // browser-side state persistence (store files, auth files, preference files).\n const browserStorageTypes = [\"local-storage\", \"session-storage\"] as const;\n const browserStorageSignals = new Map<string, Set<string>>();\n\n try {\n const storageQuery = `\n MATCH (n:Function)\n WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'\n OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'\n OR n.file_path CONTAINS 'auth' OR n.file_path CONTAINS 'Auth'\n OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'\n OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'\n OR n.file_path CONTAINS 'settings' OR n.file_path CONTAINS 'Settings'\n RETURN ${nodeColumns(\"n\")}\n `;\n\n const storageNodes = nodesFromQuery(await tools.queryGraph(storageQuery, projectName));\n for (const node of storageNodes) {\n if (!node.file_path) continue;\n\n const relFile = relativeToRoot(effectiveCodebaseRoot, node.file_path);\n\n // Heuristic: files with 'session' in name likely use sessionStorage;\n // otherwise assume localStorage (more common pattern).\n const storageKey = /session/i.test(relFile) ? \"session-storage\" : \"local-storage\";\n if (!browserStorageSignals.has(storageKey)) {\n browserStorageSignals.set(storageKey, new Set());\n }\n browserStorageSignals.get(storageKey)!.add(relFile);\n }\n } catch {\n // query_graph not available \u2014 skip browser storage detection silently\n }\n\n for (const storageType of browserStorageTypes) {\n const sourceFiles = browserStorageSignals.get(storageType);\n if (!sourceFiles || sourceFiles.size === 0) continue;\n\n const accessPattern = storageType === \"local-storage\" ? \"localStorage\" : \"sessionStorage\";\n const inferredFrom = Array.from(sourceFiles)\n .filter((f) => !this.isTestCode({ source_file: f }))\n .map((f) => ({\n source_file: f,\n import_pattern: accessPattern,\n function_patterns: [],\n }));\n\n if (inferredFrom.length > 0) {\n candidates.push({\n suggested_layer: \"data-store\",\n suggested_name: storageType,\n inferred_from: inferredFrom,\n confidence: \"low\",\n notes: `Inferred from ${inferredFrom.length} source file(s) using browser ${accessPattern} (global API, no import required)`,\n });\n }\n }\n\n return candidates;\n } finally {\n client.close();\n }\n }\n\n /**\n * Infer a datastore name from import target name, file path, node properties, or import pattern\n *\n * @private\n */\n private inferDatastoreName(\n filePath: string,\n node: CbmGraphNode,\n matchedPattern?: string,\n importTargetName?: string\n ): string | undefined {\n // Priority 1: Use import target name directly if it matched a pattern\n // This is the most reliable signal for import-based detection (e.g., \"mongodb\", \"pg\")\n if (importTargetName && matchedPattern) {\n // If the import target matches the pattern, use it as-is\n if (this.matchPattern(importTargetName, matchedPattern)) {\n return importTargetName;\n }\n }\n\n // Priority 2: Try to extract from import pattern (e.g., \"mongodb\" from pattern)\n if (matchedPattern) {\n // Extract database name from pattern like \"mongodb\", \"pg\", etc.\n const match = matchedPattern.match(/[a-z]+/i);\n if (match) {\n return match[0];\n }\n }\n\n // Priority 3: Try to extract from file name (e.g., \"users.migration.ts\" -> \"users\")\n const fileName = path.basename(filePath);\n if (fileName.includes(\"migration\") || fileName.includes(\"schema\")) {\n const match = fileName.match(/^([a-z0-9_-]+)/i);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n // Priority 4: Try to extract from node name or label\n const nodeName = String(node.label ?? node.id ?? \"\");\n\n // Check if the label is a known datastore-related type by checking mapping\n // Generic structural labels like Function, Method, Class, Module, Route should be excluded\n if (nodeName) {\n const nodeMapping = this.mapper.getNodeMapping(nodeName);\n // If the label is in the mapping and NOT a generic structural type, use it\n if (nodeMapping && nodeMapping.dr_layer !== \"api\" && nodeMapping.dr_layer !== \"application\") {\n return nodeName;\n }\n\n // Also check if it explicitly looks like a datastore name (e.g., \"Database\", \"Repository\")\n if (nodeName.toLowerCase().includes(\"database\") ||\n nodeName.toLowerCase().includes(\"repository\") ||\n nodeName.toLowerCase().includes(\"datastore\")) {\n return nodeName;\n }\n }\n\n // Fallback: use a generic name based on what was detected\n return undefined;\n }\n\n /**\n * Query for callers of a specific function or symbol\n *\n * Delegates to trace_path MCP tool with direction=\"inbound\" to find\n * all functions that call the specified qualified name. Applies depth clamping\n * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects\n * using edge-type mappings from the analyzer.\n *\n * @param projectRoot Absolute path to the project root\n * @param qualifiedName Fully qualified symbol name (e.g., \"com.example.UserService.getUser\")\n * @param depth Maximum traversal depth (default 3, clamped to max 10)\n * @param options Optional codebase configuration\n * @returns Array of callers in the call graph\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async callers(\n projectRoot: string,\n qualifiedName: string,\n depth?: number,\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n return this.traceCallPath(projectRoot, qualifiedName, depth, \"inbound\", options);\n }\n\n /**\n * Query for callees of a specific function or symbol\n *\n * Delegates to trace_path MCP tool with direction=\"outbound\" to find\n * all functions called by the specified qualified name. Applies depth clamping\n * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects\n * using edge-type mappings from the analyzer.\n *\n * @param projectRoot Absolute path to the project root\n * @param qualifiedName Fully qualified symbol name (e.g., \"com.example.UserService.getUser\")\n * @param depth Maximum traversal depth (default 3, clamped to max 10)\n * @param options Optional codebase configuration\n * @returns Array of callees in the call graph\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async callees(\n projectRoot: string,\n qualifiedName: string,\n depth?: number,\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n return this.traceCallPath(projectRoot, qualifiedName, depth, \"outbound\", options);\n }\n\n /**\n * Cypher fallback for call graph traversal when trace_path is unavailable.\n *\n * Uses query_graph directly with CALLS-edge Cypher when trace_path returns a\n * non-array nodes field (i.e. is not implemented in the binary).\n *\n * @private\n */\n private async callGraphViaCypher(\n binaryPath: string,\n projectName: string,\n qualifiedName: string,\n direction: \"outbound\" | \"inbound\",\n depth: number,\n codebaseRoot: string\n ): Promise<CallGraphNode[]> {\n // Escape single quotes for Cypher string literals (ISO SQL-style doubling)\n const safeQN = qualifiedName.replace(/'/g, \"''\");\n const limit = Math.min(depth * 20, 100);\n const cypher = direction === \"inbound\"\n ? `MATCH (caller)-[:CALLS]->(target) WHERE target.qualified_name = '${safeQN}' OR target.name = '${safeQN}' RETURN caller.id AS id, caller.qualified_name AS qualified_name, caller.file_path AS file_path LIMIT ${limit}`\n : `MATCH (source)-[:CALLS]->(callee) WHERE source.qualified_name = '${safeQN}' OR source.name = '${safeQN}' RETURN callee.id AS id, callee.qualified_name AS qualified_name, callee.file_path AS file_path LIMIT ${limit}`;\n\n if (depth > 1) {\n handleWarning(\n `Cypher CALLS fallback: depth ${depth} requested but fallback only traverses 1 hop`,\n [\"Multi-hop traversal requires trace_path support in the analyzer binary\"]\n );\n }\n\n // Spawn a fresh client \u2014 the binary closes the stdio pipe after an unimplemented\n // method call, so the caller's trace_path connection cannot be reused here.\n const client = new StdioClient();\n try {\n client.spawn(binaryPath);\n const version = await getCliVersion();\n await client.initialize({ name: \"dr-cli\", version });\n\n const queryResponse = await new CbmTools(client).queryGraph(cypher, projectName);\n\n const cols: string[] = queryResponse.columns;\n const tuples: unknown[][] = queryResponse.rows;\n return tuples.map((row) => {\n const obj: Record<string, unknown> = {};\n cols.forEach((col, i) => {\n // Strip table-alias prefix (e.g. \"caller.id\" \u2192 \"id\")\n const key = col.includes(\".\") ? col.slice(col.lastIndexOf(\".\") + 1) : col;\n obj[key] = row[i];\n });\n return shapeCallGraphNode(obj, codebaseRoot);\n });\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\n \"Cypher CALLS fallback failed \u2014 returning empty call graph\",\n [\n `Error: ${errorMsg}`,\n \"Neither trace_path nor query_graph is available for call graph traversal\",\n ]\n );\n return [];\n } finally {\n client.close();\n }\n }\n\n /**\n * Internal helper to trace call paths in either direction\n *\n * Handles common logic for callers() and callees(): pre-flight checks,\n * depth clamping, tool invocation, and response transformation.\n *\n * @private\n */\n private async traceCallPath(\n projectRoot: string,\n qualifiedName: string,\n depth: number | undefined,\n direction: \"outbound\" | \"inbound\",\n options?: AnalyzerCodebaseOptions\n ): Promise<CallGraphNode[]> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed (same pre-flight checks as endpoints)\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Clamp depth: default 3, max 10\n const clampedDepth = clampDepth(depth);\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name \u2014 trace_path requires the CBM name, not the absolute path\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Call trace_path with the clamped depth\n let trace;\n try {\n trace = await tools.tracePath({\n function_name: qualifiedName,\n direction,\n depth: clampedDepth,\n project: projectName,\n });\n } catch (error) {\n if (error instanceof CbmToolError && /not found/i.test(error.detail)) {\n throw new CLIError(\n `Symbol not found in the code graph: ${qualifiedName}`,\n ErrorCategory.NOT_FOUND,\n [\n ...(error.suggestions ?? []),\n \"Pass the fully qualified name as CBM records it (see `dr analyzer services --json`)\",\n ]\n );\n }\n throw error;\n }\n\n if (!trace) {\n // trace_path not implemented \u2014 fall back to Cypher CALLS query.\n // Pass binary_path (not client): the binary closes the pipe after an unimplemented call.\n return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, effectiveCodebaseRoot);\n }\n\n const nodes = trace.nodes;\n const edges = trace.edges;\n\n // CBM 0.11+ trace rows carry qualified names and hops only; look up their files.\n if (trace.shape === \"grouped\" && nodes.length > 0) {\n try {\n const files = await tools.filePathsOf(\n nodes.map((n) => String(n.qualified_name ?? \"\")),\n projectName\n );\n for (const node of nodes) {\n node.file_path ??= files.get(String(node.qualified_name ?? \"\"));\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n handleWarning(\"Could not look up source files for the call graph\", [`Error: ${errorMsg}`]);\n }\n }\n\n // Valid edge types come from the mapping; the first is the default for root nodes and\n // nodes without a typed incoming edge, which is CALLS regardless of the mapping's key order.\n const mappedEdgeTypes = this.mapper.getEdgeTypes();\n const validEdgeTypes = [\"CALLS\", ...mappedEdgeTypes.filter((t) => t !== \"CALLS\")];\n\n // Transform nodes to CallGraphNode objects\n const callGraphNodes: CallGraphNode[] = [];\n\n for (const node of nodes) {\n const nodeQualifiedName = String(node.qualified_name || node.id || \"\");\n\n // Shape the node using utility function\n let callGraphNode = shapeCallGraphNode(node, effectiveCodebaseRoot);\n\n // Determine edge type using utility function\n const nodeDepth = typeof node.depth === \"number\" ? node.depth : 0;\n const edgeType = determineEdgeType(\n nodeDepth,\n edges,\n nodeQualifiedName,\n validEdgeTypes\n );\n\n // Apply edge type to the shaped node\n callGraphNode.edge_type = edgeType;\n\n callGraphNodes.push(callGraphNode);\n }\n\n return callGraphNodes;\n } finally {\n client.close();\n }\n }\n\n /**\n * Shape a single CBM graph node into a route object with defaults applied\n *\n * This is extracted to be testable - applies the defaulting logic used in verify()\n * to ensure empty/missing properties are handled consistently.\n *\n * @param node Raw node from CBM graph search\n * @returns Shaped route object with defaults applied\n * @internal\n */\n shapeRoute(node: CbmGraphNode): {\n id: string;\n http_method: string;\n http_path: string;\n handler: string;\n source_file: string;\n source_symbol: string;\n } {\n const properties = node.properties ?? {};\n const nodeName = String(node.name ?? node.id ?? \"\");\n\n // Extract HTTP method: properties first, then parse from qualified_name\n // qualified_name format: __route__METHOD__/path (e.g. __route__GET__/users)\n let httpMethod = \"GET\";\n if (typeof properties.method === \"string\") {\n httpMethod = properties.method.toUpperCase();\n } else {\n const qn = String(node.qualified_name ?? \"\");\n const methodMatch = qn.match(/^__route__([A-Z]+)__/);\n const extracted = methodMatch?.[1] ?? \"\";\n if (extracted && (VALID_HTTP_METHODS as readonly string[]).includes(extracted)) {\n httpMethod = extracted;\n }\n }\n\n // Extract HTTP path: properties first, then node.name if it looks like a URL\n const httpPath = typeof properties.path === \"string\"\n ? properties.path\n : (typeof node.name === \"string\" && node.name.startsWith(\"/\") ? node.name : \"/\");\n\n const handler = String(properties.handler_name ?? \"\");\n const sourceFile = node.file_path ?? \"\";\n // Fall back to source_symbol then handler_name if symbol is absent (e.g. Python route handlers)\n const sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? properties.handler_name ?? \"\");\n\n // Construct route ID (use file:symbol as primary key for matching)\n const routeId = sourceFile && sourceSymbol\n ? `${sourceFile}:${sourceSymbol}`\n : (nodeName || node.id || \"(unknown)\");\n\n return {\n id: routeId,\n http_method: httpMethod,\n http_path: httpPath,\n handler,\n source_file: sourceFile,\n source_symbol: sourceSymbol,\n };\n }\n\n /**\n * Tier-0 classification of the indexed project (`dr analyzer classify --tier0-only`).\n *\n * Builds a card for every candidate symbol (src/analyzers/cards) and applies the Tier-0 rules\n * from this analyzer's extraction heuristics. Reads the graph only: no model calls, no writes.\n *\n * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules\n */\n async classify(projectRoot: string, options: ClassifyOptions & AnalyzerCodebaseOptions = {}): Promise<ClassifyReport> {\n const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot;\n const rules = tier0RulesFrom(this.mapper.getHeuristics());\n if (rules.length === 0) {\n throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER, [\n \"Tier-0 rules are heuristics with a parameters.tier0 block in the analyzer's extraction-heuristics.json\",\n ]);\n }\n\n if (options.labels?.length) {\n const known = new Set([...this.mapper.getNodeLabels(), ...defaultLabels(rules), ...Object.keys(CARD_FIELDS)]);\n const unknown = options.labels.filter((l) => !known.has(l));\n if (unknown.length) {\n throw new CLIError(`Unknown label${unknown.length > 1 ? \"s\" : \"\"}: ${unknown.join(\", \")}`, ErrorCategory.USER, [\n `Labels are case-sensitive; known labels: ${[...known].sort().join(\", \")}`,\n ]);\n }\n }\n\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]);\n }\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\"CBM analyzer not installed\", ErrorCategory.NOT_FOUND, [\n \"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\",\n ]);\n }\n\n const client = new StdioClient();\n try {\n client.spawn(detection.binary_path);\n await client.initialize({ name: \"dr-cli\", version: await getCliVersion() });\n const tools = new CbmTools(client);\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n return await classifyTier0(cbmCardGraph(tools, projectName), rules, this.name, {\n sourceRoot: effectiveCodebaseRoot,\n ...options,\n });\n } finally {\n client.close();\n }\n }\n\n /** The Tier-0 rules, edge mappings and node-mapping decisions extraction runs with. */\n extractionConfig(): ExtractionConfig {\n const nodeMappings = this.mapper\n .getNodeLabels()\n .map((label) => this.mapper.getNodeMapping(label))\n .filter((m): m is NonNullable<typeof m> => !!m);\n const edgeMappings = this.mapper\n .getEdgeTypes()\n .map((type) => this.mapper.getEdgeMapping(type))\n .filter((m): m is NonNullable<typeof m> => !!m);\n return extractionConfigFrom(tier0RulesFrom(this.mapper.getHeuristics()), edgeMappings, nodeMappings);\n }\n\n /**\n * What `codebase_extract` reads from the index (src/analyzers/cards/extract.ts): cards with\n * their Tier-0 outcomes, the mapped edges and the symbols of the files asked about. Read-only.\n * Graph reads get two minutes each, past the client's 30s default, for large graphs.\n *\n * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules\n */\n async extract(projectRoot: string, options: ExtractOptions & AnalyzerCodebaseOptions = {}): Promise<ExtractionGraph> {\n const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot;\n const config = this.extractionConfig();\n if (config.rules.length === 0) {\n throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER);\n }\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [\"Run `dr analyzer index` to index the project\"]);\n }\n if (!status.detected.installed) {\n throw new CLIError(\"CBM analyzer not installed\", ErrorCategory.NOT_FOUND, [\n \"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\",\n ]);\n }\n const client = new StdioClient();\n try {\n client.spawn(status.detected.binary_path);\n await client.initialize({ name: \"dr-cli\", version: await getCliVersion() });\n const tools = new CbmTools(client, { timeout: EXTRACT_CALL_TIMEOUT_MS });\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n return await collectExtraction(cbmCardGraph(tools, projectName), config, this.name, {\n sourceRoot: effectiveCodebaseRoot,\n ...options,\n });\n } finally {\n client.close();\n }\n }\n\n /**\n * Verify that graph-discovered routes align with model endpoints\n *\n * Queries the CBM graph for Route nodes, shapes them via the Route mapping,\n * and delegates comparison to VerifyEngine. Entirely read-only.\n *\n * @param projectRoot Absolute path to the project root\n * @param options Verification options including codebase root\n * @returns Comprehensive verification report\n * @throws CLIError if project not indexed or analyzer not installed\n */\n async verify(\n projectRoot: string,\n options: VerifyOptions & AnalyzerCodebaseOptions\n ): Promise<VerifyReport> {\n // Resolve the effective codebase root\n const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot;\n\n // Check that the project is indexed\n const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });\n if (!status.indexed) {\n throw new CLIError(\n `Project not indexed: ${projectRoot}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Run `dr analyzer index` to index the project\",\n \"Ensure codebase-memory-mcp is installed and working\",\n ]\n );\n }\n\n // Use detection result from status() instead of re-detecting\n const detection = status.detected;\n if (!detection.installed) {\n throw new CLIError(\n \"CBM analyzer not installed\",\n ErrorCategory.NOT_FOUND,\n [\"Install codebase-memory-mcp: npm install -g codebase-memory-mcp\"]\n );\n }\n\n // Get the Route mapping BEFORE initializing client to validate it exists\n const routeMapping = this.mapper.getNodeMapping(\"Route\");\n if (!routeMapping) {\n throw new CLIError(\n \"Route mapping not found in analyzer\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n const client = new StdioClient();\n\n try {\n client.spawn(detection.binary_path);\n\n const version = await getCliVersion();\n await client.initialize({\n name: \"dr-cli\",\n version,\n });\n\n const tools = new CbmTools(client);\n\n // Resolve internal project name for search_graph (requires name, not absolute path)\n const projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);\n\n // Search for Route nodes using the CBM label from the mapping\n const allNodes = await this.fetchRoutes(tools, projectName, routeMapping.cbm_label);\n // Keep only nodes whose qualified_name follows the __route__METHOD__/path\n // convention. Nodes that don't match are graph artifacts (documentation\n // strings, base URLs, etc.) that were incidentally labelled Route by the\n // indexer \u2014 not actual HTTP routes.\n const nodes = allNodes\n .filter((n) => /^__route__[A-Z]+__\\//.test(String(n.qualified_name ?? \"\")))\n .filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? \"\")));\n\n // Shape routes via mapping; drop unattributed nodes (same filter as endpoints())\n const routes = nodes\n .map((node) => this.shapeRoute(node))\n .filter((r) => r.source_file);\n\n // Delegate to VerifyEngine\n const engine = new VerifyEngine();\n const indexMeta = status.index_meta;\n const analyzerName = this.name;\n const report = await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);\n\n return report;\n } finally {\n client.close();\n }\n }\n}\n\n/** The heading DR writes above the lines it adds to .cbmignore. */\nconst CBM_IGNORE_HEADING =\n \"# Added by Documentation Robotics: its model, changesets and analyzer state are not code.\";\n\n/**\n * Makes sure codebase-memory-mcp leaves DR's own folders ({@link DR_OWN_DIRS}: documentation-robotics/\n * and .dr/) out of the index. codebase-memory-mcp has no exclude argument; it skips what .gitignore,\n * the repository's info/exclude and a `.cbmignore` at the indexed root list (gitignore syntax).\n * DR's folders are meant to be committed, so they can't go in the first two; `.cbmignore` is read\n * by the analyzer alone.\n *\n * Adds a line for each DR folder inside the codebase root that the file doesn't already list,\n * creating the file when needed; a folder outside the codebase root isn't indexed anyway, and a\n * folder the file negates (`!/documentation-robotics/`) is the developer's choice. Returns\n * the file's path and the lines it added (none when it already had them).\n */\nexport async function ensureCbmIgnore(\n codebaseRoot: string,\n projectRoot: string\n): Promise<{ path: string; added: string[]; created: boolean }> {\n const file = path.join(codebaseRoot, \".cbmignore\");\n let text = \"\";\n let created = true;\n try {\n text = await readFile(file, \"utf-8\");\n created = false;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n const lines = text\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith(\"#\"));\n const folder = (line: string) => line.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const listed = new Set(lines.filter((line) => !line.startsWith(\"!\")).map(folder));\n // A negation (`!/documentation-robotics/`) is the developer asking for the folder to be\n // indexed: leave it alone.\n const negated = new Set(lines.filter((line) => line.startsWith(\"!\")).map((line) => folder(line.slice(1))));\n const added: string[] = [];\n for (const dir of DR_OWN_DIRS) {\n const relative = path.relative(codebaseRoot, path.join(projectRoot, dir)).split(path.sep).join(\"/\");\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative)) continue;\n if (!listed.has(relative) && !negated.has(relative)) added.push(`/${relative}/`);\n }\n if (added.length === 0) return { path: file, added, created: false };\n\n const separator = text.length === 0 ? \"\" : text.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const heading = text.includes(CBM_IGNORE_HEADING) ? \"\" : `${CBM_IGNORE_HEADING}\\n`;\n await writeFile(file, `${text}${separator}${heading}${added.join(\"\\n\")}\\n`);\n return { path: file, added, created };\n}\n"],
|
|
5
|
+
"mappings": "AAaA,SAAS,iBAAiB;AAC1B,YAAY,UAAU;AACtB,SAAS,UAAU,iBAAiB;AACpC,SAAS,UAAU,eAAe,qBAAqB;AAiBvD,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,eAAe,sBAAsB;AAE9C,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,YAAY,oBAAoB,yBAAyB;AAClE,SAAS,mBAAmB,eAAe,cAAc,8BAA8B;AACvF,SAAS,mBAAmB;AAC5B,SACE,UACA,cACA,sBACA,qBACA,aACA,gBACA,gBACA,8BACK;AAEP,SAAS,oBAAoB;AAC7B,SAAS,eAAe,qBAAgE;AACxF,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB,4BAA8F;AAG1H,MAAM,0BAA0B,MAMnB,qBAAqB;AAMlC,SAAS,kBAAkB,eAAqB;AAC9C,QAAM,WAAW,cAAc,QAAQ,sBAAsB,EAAE;AAG/D,SAAO,CAAC,mBAAmB,KAAK,QAAQ,KACnC,CAAC,SAAS,WAAW,OAAO,KAC5B,CAAC,SAAS,WAAW,WAAW;AACvC;AAMA,SAAS,gCAAgC,MAAY;AACnD,QAAM,QAAQ,KAAK,YAAW;AAC9B,SAAI,iCAAiC,KAAK,KAAK,IAAU,SACrD,iCAAiC,KAAK,KAAK,IAAU,QACrD,iBAAiB,KAAK,KAAK,IAAU,UACrC,2BAA2B,KAAK,KAAK,IAAU,WAC5C;AACT;AAMA,SAAS,8BAA8B,MAAY;AACjD,QAAM,WAAW,KAAK,QAAQ,iHAAiH,EAAE,GAC3I,UAAU,YAAY,YAAY,IAAI;AAC5C,SAAO,UAAU,IAAI,OAAO,KAAK;AACnC;AAGA,MAAM,uBAAuB,oBAAI,IAAI,CAAC,uBAAuB,wBAAwB,oBAAoB,CAAC;AAKpG,MAAO,YAAW;EACd;EACA,wBAAwB;EACxB,mBAAmB,oBAAI,IAAG;;;;;EAM1B,OAAgB,wBAAwB;IAC9C;;EAGF,YAAY,QAAqB;AAC/B,SAAK,SAAS;EAChB;;;;EAKA,IAAI,OAAI;AACN,WAAO,KAAK,OAAO,gBAAe;EACpC;;;;EAKA,IAAI,cAAW;AAEb,WADiB,KAAK,OAAO,oBAAmB,GAC/B,gBAAgB,KAAK,OAAO,gBAAe;EAC9D;;;;;;;;;;;EAYA,MAAM,qBAAqB,aAAmB;AAC5C,QAAI;AACF,YAAM,cAAc,KAAK,KAAK,aAAa,WAAW,GAChD,aAAa,MAAM,SAAS,aAAa,OAAO,GAChD,YAAY,KAAK,MAAM,UAAU,GAEjC,gBADW,KAAK,OAAO,oBAAmB,GAChB,mBAAmB;AAEnD,aAAQ,UAAU,cAAc,iBAAiB,UAAU,cAAe;IAC5E,SAAS,OAAO;AAEd,YAAM,MAAM;AACZ,UAAI,IAAI,SAAS;AACf,eAAO;AAIT,YAAI,IAAI,SAAS,WACT,IAAI,SACR,uCACA,cAAc,QACd;QACE,eAAe,KAAK,KAAK,aAAa,WAAW,CAAC;QAClD;OACD,IAKC,IAAI,SACR,4BACA,cAAc,YACd;QACE,cAAc,KAAK,KAAK,aAAa,WAAW,CAAC;QACjD,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;OACjE;IAEL;EACF;;;;;;;;;;EAWA,MAAM,OAAO,aAAoB;AAE/B,UAAM,WAAW,KAAK,OAAO,oBAAmB,GAC1C,cAAe,UAAU,gBAAyC;MACtE;;AAKF,QAAI,gBAAgB;AACpB,QAAI;AACF,sBAAgB,MAAM,KAAK,qBAAqB,eAAe,QAAQ,IAAG,CAAE;IAC9E,SAAS,OAAO;AAGd,UAAI,iBAAiB;AACnB,sBAAc,MAAM,SAAS,MAAM,WAAW;WACzC;AAEL,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBAAc,8CAA8C,CAAC,QAAQ,CAAC;MACxE;AACA,sBAAgB;IAClB;AAEA,eAAW,cAAc,aAAa;AAEpC,YAAM,cAAc,UAAU,SAAS,CAAC,UAAU,GAAG;QACnD,OAAO;QACP,UAAU;OACX;AAED,UAAI,YAAY,WAAW;AACzB;AAGF,YAAM,aAAa,YAAY,OAAO,KAAI;AAE1C,UAAI,CAAC;AACH;AAIF,YAAM,SAAS,IAAI,YAAW;AAC9B,UAAI;AACF,eAAO,MAAM,UAAU;AAEvB,cAAM,UAAU,MAAM,cAAa;AACnC,cAAM,OAAO,WAAW;UACtB,MAAM;UACN;SACD;AAGD,YAAI,aAAa;AACjB,cAAM,gBAAiB,UAAU,yBAAiE,kBAA0C,CAAA;AAE5I,YAAI,cAAc,SAAS;AACzB,cAAI;AAGF,kBAAM,WAAW,MAAM,OAAO,SAAS,cAAc,CAAA,CAAE,GAEjD,iBAAiB,IAAI,KACxB,SAAS,SAAS,CAAA,GAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC;AAGjD,uBAAW,QAAQ;AACjB,cAAK,eAAe,IAAI,IAAI,MAC1B,aAAa,IACb,cACE,kBAAkB,IAAI,sBAAsB,UAAU,IACtD,CAAC,uDAAuD,CAAC;UAIjE,SAAS,OAAO;AAEd,yBAAa;AACb,kBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,0BACE,uCAAuC,UAAU,KAAK,QAAQ,IAC9D,CAAC,gEAAgE,CAAC;UAEtE;AAIF,eAAO;UACL,WAAW;UACX,aAAa;UACb,SAAS;;UACT,gBAAgB;UAChB,aAAa;;MAEjB,SAAS,OAAO;AAEd,cAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,sBACE,UAAU,UAAU,2BAA2B,YAAY,IAC3D,CAAC,4DAA4D,CAAC;AAEhE;MACF;AAEE,YAAI;AACF,iBAAO,MAAK;QACd,SAAS,OAAO;AACd,gBAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,wBACE,kCAAkC,UAAU,KAAK,YAAY,IAC7D;YACE;YACA;WACD;QAEL;MACF;IACF;AAGA,WAAO;MACL,WAAW;;EAEf;;;;;;;;;;EAWA,MAAM,OAAO,aAAqB,SAAiC;AACjE,UAAM,WAAW,MAAM,KAAK,OAAO,WAAW,GAGxC,wBAAwB,SAAS,gBAAgB,aAGjD,eAAe,KAAK,OAAO,gBAAe,GAC1C,YAAY,MAAM,cAAc,aAAa,YAAY;AAK/D,QAAI,EAFY,cAAc;AAG5B,aAAO;QACL;QACA,SAAS;QACT,OAAO;;AAMX,QAAI,QAAQ;AACZ,QAAI;AACF,YAAM,oBAAoB,UAAU,OAAO,CAAC,aAAa,MAAM,GAAG;QAChE,KAAK;QACL,OAAO;QACP,UAAU;OACX;AAED,UAAI,kBAAkB,WAAW;AAE/B,gBADoB,kBAAkB,OAAO,KAAI,MACzB,UAAU;WAC7B;AAEL,cAAM,WAAW,kBAAkB,QAAQ,KAAI,KAAM;AACrD,QAAI,SAAS,eACX,cAAc,kDAAkD;UAC9D,kBAAkB,qBAAqB;UACvC,cAAc,QAAQ;UACtB;SACD,IAED,cAAc,iCAAiC;UAC7C,cAAc,QAAQ;UACtB;SACD,GAEH,QAAQ;MACV;IACF,SAAS,OAAO;AAEd,YAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,oBAAc,+BAA+B;QAC3C,UAAU,QAAQ;QAClB;OACD,GACD,QAAQ;IACV;AAEA,WAAO;MACL;MACA,SAAS;MACT,YAAY;MACZ;MACA,cAAc,UAAU;;EAE5B;;;;;;;;;;;;;EAcA,MAAM,MACJ,aACA,SAA2E;AAG3E,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AAGrF,QAAI,OAAO,WAAW,OAAO,SAAS,CAAC,SAAS,OAAO;AACrD,oBAAc,2CAA2C;QACvD,iBAAiB,OAAO,YAAY;OACrC;AAGD,YAAM,OAAO,OAAO;AACpB,aAAO;QACL,YAAY,KAAK;QACjB,YAAY,KAAK;QACjB,UAAU,KAAK;QACf,WAAW,KAAK;;IAEpB;AAGA,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd;QACE;QACA;OACD;AAIL,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAED,YAAM,QAAQ,IAAI,SAAS,MAAM;AAUjC,WANiB,MAAM,MAAM,aAAY,GACV,KAAK,CAAC,MAAM,EAAE,cAAc,qBAAqB,KAK3D,OAAO,SAAS,CAAC,SAAS,OAAO;AAQpD,YAPA,cACE,4DACA,CAAC,qCAAqC,CAAC,GAKrC,CAAC,OAAO;AACV,gBAAM,IAAI,SACR,+DACA,cAAc,YACd;YACE;YACA;WACD;AAIL,cAAMA,QAAO,OAAO;AACpB,eAAO;UACL,YAAYA,MAAK;UACjB,YAAYA,MAAK;UACjB,UAAUA,MAAK;UACf,WAAWA,MAAK;;MAEpB;AAIA,YAAM,aAAa,UAAU,OAAO,CAAC,aAAa,MAAM,GAAG;QACzD,KAAK;QACL,OAAO;QACP,UAAU;OACX;AAED,UAAI,WAAW,WAAW;AACxB,cAAM,IAAI,SACR,0BACA,cAAc,QACd;UACE;UACA,kBAAkB,qBAAqB;UACvC;UACA,UAAU,WAAW,QAAQ,KAAI,KAAM,eAAe;SACvD;AAIL,YAAM,UAAU,WAAW,OAAO,KAAI;AAGtC,UAAI;AAEF,cAAM,oBAAoB,MADH,IAAI,mBAAmB,WAAW,EACV,YAAW;AAC1D,QAAI,qBACF,QAAQ,IAAI,mCAAmC,iBAAiB,EAAE;MAEtE,SAAS,OAAO;AAEd,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBACE,mCACA,CAAC,UAAU,QAAQ,IAAI,0BAA0B,CAAC;MAEtD;AAGA,UAAI;AACF,cAAM,SAAS,MAAM,gBAAgB,uBAAuB,WAAW;AACvE,QAAI,OAAO,MAAM,SAAS,KACxB,QAAQ,IAAI,SAAS,OAAO,MAAM,KAAK,IAAI,CAAC,OAAO,OAAO,IAAI,yCAAyC;MAE3G,SAAS,OAAO;AACd,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBAAc,8DAA8D,CAAC,UAAU,QAAQ,EAAE,CAAC;MACpG;AAGA,YAAM,aAAY,oBAAI,KAAI,GAAG,YAAW,GAElC,gBAAgB,MAAM,MAAM,gBAChC,uBACA,SAAS,YAAY,EAAE,SAAS,QAAQ,UAAS,IAAK,MAAS,GAG3D,YAAY,OAAO,cAAc,SAAU,WAAW,cAAc,QAAQ,GAC5E,YAAY,OAAO,cAAc,SAAU,WAAW,cAAc,QAAQ;AAGlF,MAAI,OAAO,cAAc,SAAU,YACjC,cACE,4CACA;QACE,wBAAwB,OAAO,cAAc,KAAK,KAAK,OAAO,cAAc,KAAK,CAAC;QAClF;OACD,GAID,OAAO,cAAc,SAAU,YACjC,cACE,4CACA;QACE,wBAAwB,OAAO,cAAc,KAAK,KAAK,OAAO,cAAc,KAAK,CAAC;QAClF;OACD;AAKL,YAAM,OAAkB;QACtB,UAAU;QACV;QACA,YAAY;QACZ,YAAY;SAGR,eAAe,KAAK,OAAO,gBAAe;AAChD,mBAAM,eAAe,MAAM,aAAa,YAAY,GAE7C;QACL,YAAY;QACZ,YAAY;QACZ,UAAU;QACV;;IAEJ;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;;EAWQ,MAAM,YAAY,OAAiB,aAAqB,OAAa;AAC3E,UAAM,SAAS,MAAM,MAAM,YAAY,EAAE,OAAO,SAAS,YAAW,CAAE;AACtE,QAAI;AACF,YAAM,WAAW,uBAAuB,MAAM,MAAM,WAAW,sBAAsB,WAAW,CAAC;AACjG,aAAO,oBAAoB,QAAQ,QAAQ;IAC7C,SAAS,OAAO;AACd,YAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,2BAAc,8EAA8E;QAC1F,UAAU,QAAQ;OACnB,GACM;IACT;EACF;;;;;;;;EASQ,MAAM,mBACZ,QACA,cAAoB;AAIpB,UAAM,SADW,OADH,kBAAkB,WAAW,SAAS,IAAI,SAAS,MAAM,GAC1C,aAAY,GAClB,KAAK,CAAC,MAAM,EAAE,cAAc,YAAY;AAC/D,WAAK,QAUE,MAAM,QATX,cACE,+EACA;MACE,kBAAkB,YAAY;MAC9B;KACD,GAEI;EAGX;;;;;;;;;;;;EAaA,MAAM,UAAU,aAAqB,SAAiC;AAEpE,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAKvE,UAAM,eAAe,KAAK,OAAO,eAAe,OAAO;AACvD,QAAI,CAAC;AACH,YAAM,IAAI,SACR,uCACA,cAAc,YACd,CAAC,8DAA8D,CAAC;AAIpE,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAED,YAAM,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB,GAQxE,SALW,MAAM,KAAK,YAAY,OAAO,aAAa,aAAa,SAAS,GAM/E,OAAO,CAAC,MAAM,uBAAuB,KAAK,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,EACzE,OAAO,CAAC,MAAM,kBAAkB,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,GAG5D,aAAkC,CAAA;AAExC,iBAAW,QAAQ,OAAO;AACxB,cAAM,YAAY,MAAM,KAAK,wBAC3B,MACA,cACA,qBAAqB;AAKvB,QAAK,UAAU,gBAIV,KAAK,WAAW,SAAS,KAC5B,WAAW,KAAK,SAAS;MAE7B;AAMA,UAAI,WAAW,WAAW;AACxB,YAAI;AACF,gBAAM,cAAc;;;;;;;;;;;qBAWT,YAAY,GAAG,CAAC;aAGrB,cAAc,eAAe,MAAM,MAAM,WAAW,aAAa,WAAW,CAAC;AACnF,qBAAW,QAAQ,aAAa;AAC9B,gBAAI,CAAC,KAAK;AAAW;AACrB,kBAAM,YAAY,KAAK,wBAAwB,MAAM,qBAAqB;AAC1E,YAAI,aAAa,CAAC,KAAK,WAAW,SAAS,KACzC,WAAW,KAAK,SAAS;UAE7B;QACF,SAAS,OAAO;AAEd,gBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,wBAAc,6DAA6D,CAAC,UAAU,QAAQ,EAAE,CAAC;QACnG;AAGF,kBAAK,2BACH,YACA,CAAC,MAAM,EAAE,uBACT,CAAC,MAAM,EAAE,iBAAiB,EAAE,eAAe,WAAW,GAEjD;IACT;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;;;;;;EAeA,MAAM,MAAM,aAAqB,UAAkB,SAAiC;AAElF,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAIvE,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAED,YAAM,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB;AAG9E,UAAI;AACF,cAAM,SAAS,MAAM,MAAM,WAAW,UAAU,WAAW;AAC3D,eAAO,EAAE,GAAG,QAAQ,OAAO,OAAO,KAAK,OAAM;MAC/C,SAAS,OAAO;AAEd,YAAI,iBAAiB;AACnB,gBAAM,IAAI,SAAS,iBAAiB,MAAM,MAAM,IAAI,cAAc,MAAM,MAAM,WAAW;AAI3F,cAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAE1E,cAAI,aAAa,SAAS,aAAa,KAAK,aAAa,SAAS,gBAAgB,IAC1E,IAAI,SACR,8CACA,cAAc,MACd;UACE;UACA;UACA;SACD,IAKC;MACR;IACF;AACE,aAAO,MAAK;IACd;EACF;EAEQ,2BACN,OACA,QACA,UAA6B;AAE7B,UAAM,OAAO,oBAAI,IAAG;AACpB,eAAW,QAAQ,OAAO;AACxB,YAAM,MAAM,OAAO,IAAI;AACvB,UAAI,CAAC;AAAK;AACV,YAAM,QAAQ,SAAS,IAAI;AAC3B,MAAI,KAAK,IAAI,GAAG,IACd,cACE,2BAA2B,GAAG,wDAC9B;QACE,UAAU,KAAK,IAAI,GAAG,CAAE;QACxB,aAAa,KAAK;QAClB;OACD,IAGH,KAAK,IAAI,KAAK,KAAK;IAEvB;EACF;;;;;;;;;;;;;;;;;;;EAoBQ,MAAM,wBACZ,MACA,SACA,cAAoB;AAEpB,UAAM,aAAa,KAAK,cAAc,CAAA,GAEhC,SAAS,OAAO,KAAK,QAAQ,KAAK,MAAM,WAAW;AAGzD,QAAI,aAAa,QAAQ;AAGzB,UAAM,gBAAgB,YAAY,OAAO,WAAW,QAAQ,KAAK,QAAQ,KAAK,MAAM,EAAE,CAAC,GAGjF,sBAAsB;AAI5B,QAAI;AACJ,IAAI,OAAO,WAAW,UAAW,WAC/B,YAAY,WAAW,OAAO,YAAW,KAIzC,YAFW,OAAO,KAAK,kBAAkB,EAAE,EACpB,MAAM,sBAAsB,IACzB,CAAC,KAAK,OAC5B,cAAc,UAAO,YAAY;AAIvC,UAAM,eAAe,IAAI,IAAI,kBAAkB,GACzC,gBAAgB,aAAa,IAAI,SAAuB,GACxD,aAAyB,gBAC1B,YACD;AAEJ,IAAK,iBACH,cACE,QAAQ,MAAM,yBACd;MACE,WAAW,SAAS;MACpB,kBAAkB,MAAM,KAAK,YAAY,EAAE,KAAK,IAAI,CAAC;MACrD;KACD;AAKL,QAAI;AACJ,IAAI,OAAO,WAAW,QAAS,WAC7B,WAAW,WAAW,OACb,OAAO,KAAK,QAAS,YAAY,KAAK,KAAK,WAAW,GAAG,IAClE,WAAW,KAAK,QAEhB,cACE,QAAQ,MAAM,kCACd;MACE,wBAAwB,OAAO,WAAW,IAAI;MAC9C;KACD,GAEH,WAAW;AAKb,UAAM,uBAAuB,OAC3B,WAAW,gBACX,WAAW,0BACX,EAAE,GAEE,eAAe,OAAO,WAAW,UAAU,WAAW,iBAAiB,EAAE,GACzE,kBAAkB,OAAO,WAAW,cAAc,CAAC,GACnD,gBAAgB,OAAO,WAAW,YAAY,CAAC;AAGrD,KAAI,CAAC,wBAAwB,CAAC,kBAC5B,aAAa,cAAc,UAAU;AAIvC,QAAI,aAAa,OAAO,KAAK,aAAa,WAAW,aAAa,WAAW,eAAe,EAAE;AAC9F,QAAI,cAAc;AAChB,UAAI;AACF,qBAAa,eAAe,cAAc,UAAU;MACtD,SAAS,OAAO;AAEd,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBACE,uCAAuC,MAAM,IAC7C;UACE,UAAU,QAAQ;UAClB;SACD;MAGL;AAGF,WAAO;MACL,aAAa;MACb;MACA,iBAAiB;MACjB,wBAAwB;MACxB,gBAAgB;MAChB,uBAAuB;MACvB,aAAa;MACb,WAAW;MACX,wBAAwB;MACxB,eAAe;MACf,mBAAmB;MACnB,iBAAiB;MACjB,kBAAkB;QAChB,YAAY;QACZ,WAAW,aACP;UACE;YACE,MAAM;YACN,QAAQ,gBAAgB;;YAG5B;;;EAGV;;;;;;;;;;;;;;;;;EAoBQ,wBAAwB,MAAoB,cAAoB;AACtE,UAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,MAAM,EAAE;AAC9C,QAAI,CAAC;AAAM,aAAO;AAElB,QAAI,aAAa,OAAO,KAAK,aAAa,EAAE;AAC5C,QAAI,cAAc;AAChB,UAAI;AACF,qBAAa,eAAe,cAAc,UAAU;MACtD,QAAQ;MAER;AAEF,QAAI,CAAC;AAAY,aAAO;AAExB,UAAM,aAAa,gCAAgC,IAAI,GACjD,WAAW,8BAA8B,IAAI,GAC7C,gBAAgB,YAAY,IAAI;AAEtC,WAAO;MACL,aAAa;MACb,YAAY;MACZ,iBAAiB;MACjB,wBAAwB;MACxB,gBAAgB;MAChB,uBAAuB;MACvB,aAAa;MACb,WAAW;MACX,wBAAwB;MACxB,eAAe;MACf,mBAAmB,OAAO,KAAK,YAAY,cAAc,CAAC;MAC1D,iBAAiB,OAAO,KAAK,YAAY,YAAY,CAAC;MACtD,kBAAkB;QAChB,YAAY;QACZ,WAAW,CAAC,EAAE,MAAM,YAAY,QAAQ,KAAI,CAAE;;;EAGpD;EAEQ,WAAW,WAAkC;AAKnD,UAAM,eAHiB,KAAK,OAAO,kBAAiB,EAGhB,KAClC,CAAC,SAAS,KAAK,SAAS,yBAAyB,KAAK,OAAO;AAI/D,QAAI,CAAC,gBAAgB,CAAC,aAAa;AACjC,aAAO,YAAY,sBAAsB,KAAK,CAAC,YAC7C,QAAQ,KAAK,UAAU,WAAW,CAAC;AAKvC,QAAI;AAEF,aADwB,IAAI,OAAO,aAAa,OAAO,EAChC,KAAK,UAAU,WAAW;IACnD,SAAS,OAAO;AAGd,UAAI,CAAC,KAAK,uBAAuB;AAC/B,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBACE,iEACA;UACE,YAAY,aAAa,OAAO;UAChC,UAAU,QAAQ;UAClB;SACD,GAEH,KAAK,wBAAwB;MAC/B;AACA,aAAO,YAAY,sBAAsB,KAAK,CAAC,YAC7C,QAAQ,KAAK,UAAU,WAAW,CAAC;IAEvC;EACF;;;;;;;;;;;;;;;EAgBA,MAAM,SAAS,aAAqB,SAAiC;AAEnE,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAIvE,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAGD,YAAM,aAAa,KAAK,OAAO,cAAa,GACtC,oBAA8B,CAAA;AAGpC,iBAAW,SAAS,YAAY;AAC9B,cAAM,UAAU,KAAK,OAAO,eAAe,KAAK;AAGhD,QAAI,WAAW,QAAQ,aAAa,iBAAiB,qBAAqB,IAAI,QAAQ,eAAe,KACnG,kBAAkB,KAAK,KAAK;MAEhC;AAGA,UAAI,kBAAkB,WAAW;AAC/B,6BACE,yDACA;UACE;UACA;UACA;SACD,GAEI,CAAA;AAGT,YAAM,aAAiC,CAAA,GAEjC,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB,GAIxE,gBAAwC,EAAE,OAAO,GAAG,QAAQ,EAAC;AACnE,wBAAkB,KAAK,CAAC,GAAG,OAAO,cAAc,CAAC,KAAK,OAAO,cAAc,CAAC,KAAK,GAAG;AAGpF,iBAAW,SAAS,mBAAmB;AACrC,cAAM,QAAQ,MAAM,MAAM,YAAY,EAAE,OAAO,SAAS,YAAW,CAAE,GAG/D,cAAc,KAAK,OAAO,eAAe,KAAK;AACpD,YAAI,CAAC;AACH;AAIF,cAAM,0BAA0B,YAAY,wBAAwB,CAAA;AAGpE,mBAAW,QAAQ,OAAO;AACxB,gBAAM,YAAY,MAAM,KAAK,uBAC3B,MACA,aACA,uBACA,uBAAuB;AAKzB,UAAI,UAAU,sBAAsB,SAAS,KAAK,CAAC,KAAK,WAAW,SAAS,KAC1E,WAAW,KAAK,SAAS;QAE7B;MACF;AAIA,YAAM,YAAY,oBAAI,IAAG,GACnB,UAAU,WAAW,OAAO,CAAC,MAC5B,EAAE,cACH,UAAU,IAAI,EAAE,WAAW,IAAU,MACzC,UAAU,IAAI,EAAE,WAAW,GACpB,MAHoB,EAI5B;AACD,kBAAK,2BACH,SACA,CAAC,MAAM,EAAE,uBACT,CAAC,MAAM,EAAE,iBAAiB,EAAE,eAAe,WAAW,GAEjD;IACT;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;;;EAYQ,MAAM,uBACZ,MACA,SACA,cACA,yBAAiC;AAEjC,UAAM,aAAa,KAAK,cAAc,CAAA,GAGhC,gBAAgB,YAAY,OAAO,WAAW,QAAQ,KAAK,QAAQ,KAAK,MAAM,EAAE,CAAC,GAGjF,sBAAsB;AAG5B,QAAI,aAAa,KAAK,aAAa;AACnC,QAAI,cAAc;AAChB,UAAI;AACF,qBAAa,eAAe,cAAc,UAAU;MACtD,SAAS,OAAO;AAEd,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBACE,uCAAuC,OAAO,KAAK,QAAQ,KAAK,MAAM,WAAW,CAAC,IAClF;UACE,UAAU,QAAQ;UAClB;SACD;MAGL;AAIF,UAAM,gBAAgB,OAAO,WAAW,kBAAkB,KAAK,kBAAkB,KAAK,QAAQ,KAAK,MAAM,EAAE,GAGrG,QAAQ,OAAO,WAAW,UAAW,WAAW,WAAW,SAAS,GACpE,SAAS,OAAO,WAAW,WAAY,WAAW,WAAW,UAAU,GAGvE,uBAAiC,CAAA;AACvC,QAAI,eAAe;AAEnB,eAAW,iBAAiB,yBAAyB;AACnD,YAAM,YAAY,KAAK,OAAO,aAAa,aAAa;AACxD,UAAI,CAAC,WAAW;AAEd,QAAK,KAAK,iBAAiB,IAAI,aAAa,MAC1C,KAAK,iBAAiB,IAAI,aAAa,GACvC,cACE,wBAAwB,aAAa,mCACrC;UACE;UACA,yBAAyB,uBAAsB,EAAG,KAAK,IAAI,CAAC;UAC5D;SACD;AAGL;MACF;AAIA,MADc,KAAK,kBAAkB,WAAW,MAAM,UAAU,MAE9D,qBAAqB,KAAK,aAAa,GAEnC,kBAAkB,qBACpB,eAAe;IAGrB;AAIA,QAAI,EADe,qBAAqB,SAAS,KAAK;AAQpD,aAAO;QACL,iBAAiB;QACjB,wBALA,QAAQ,6BAA6B,yBACjC,yBACA;QAIJ,uBAAuB;QACvB,gBAAgB;QAChB,aAAa;QACb,eAAe,OAAO,WAAW,QAAQ,KAAK,QAAQ,KAAK,MAAM,EAAE;QACnE,gBAAgB;QAChB,uBAAuB,CAAA;QACvB,YAAY;QACZ,QAAQ;QACR,SAAS;;AAKb,UAAM,aAAa,cACjB,QAAQ,UAAuC;AASjD,WAAO;MACL,iBAAiB;MACjB,wBANA,QAAQ,6BAA6B,yBACjC,yBACA;MAKJ,uBAAuB;MACvB,gBAAgB;MAChB,aAAa;MACb,eAAe,OAAO,WAAW,QAAQ,KAAK,QAAQ,KAAK,MAAM,EAAE;MACnE,gBAAgB;MAChB,uBAAuB;MACvB;MACA,QAAQ;MACR,SAAS;;EAEb;;;;;;;;;;;;;EAcQ,kBACN,WACA,MACA,YAAkB;AAElB,UAAM,aAAa,KAAK,cAAc,CAAA,GAGhC,SAAS,kBAAkB,WAAW,YAAY,UAAU;AAGlE,WAAI,UAAU,QAAQ,CAAC,uBAAsB,EAAG,SAAS,UAAU,IAAI,MAEhE,KAAK,iBAAiB,IAAI,UAAU,IAAI,MAC3C,cACE,2BAA2B,UAAU,IAAI,IACzC;MACE;MACA;MACA;KACD,GAEH,KAAK,iBAAiB,IAAI,UAAU,IAAI,KAIrC;EACT;;;;;;;;;EAUQ,aAAa,UAAkB,SAAe;AACpD,WAAO,aAAa,UAAU,OAAO;EACvC;;;;;;;;;;;;;EAcA,MAAM,WAAW,aAAqB,SAAiC;AAErE,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAIvE,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAGD,YAAM,qBAAqB,KAAK,OAAO,aAAa,qBAAqB;AACzE,UAAI,CAAC;AACH,cAAM,IAAI,SACR,uDACA,cAAc,YACd;UACE;UACA;SACD;AAIL,YAAM,iBAAiB,mBAAmB,YAAY,YAAY,CAAA,GAC5D,mBAAmB,mBAAmB,YAAY,qBAAqB,CAAA,GAGvE,mBAAmB,oBAAI,IAAG,GAU1B,uBAAuB,oBAAI,IAAG,GAE9B,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB;AAI9E,UAAI,WAA2B,CAAA;AAC/B,UAAI;AAEF,cAAM,eAAe;;4BAED,YAAY,IAAI,CAAC;;;4BAGjB,YAAY,IAAI,CAAC;;AAGrC,mBAAW,eAAe,MAAM,MAAM,WAAW,cAAc,WAAW,CAAC;MAC7E,SAAS,OAAO;AAGd,cAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBACE,iFACA;UACE,UAAU,QAAQ;UAClB;UACA;UACA;SACD;AAIH,cAAM,aAAa,KAAK,OAAO,cAAa;AAC5C,mBAAW,SAAS;AAClB,mBAAS,KAAK,GAAI,MAAM,MAAM,YAAY,EAAE,OAAO,SAAS,YAAW,CAAE,CAAE;MAE/E;AAGA,iBAAW,QAAQ,UAAU;AAC3B,cAAM,WAAW,KAAK,aAAa,IAI7B,mBAAmB,OAAO,KAAK,SAAS,KAAK,YAAY,QAAQ,KAAK,MAAM,EAAE,EAAE,YAAW;AAGjG,YAAI;AACJ,mBAAW,WAAW;AACpB,cAAI,KAAK,aAAa,kBAAkB,OAAO,GAAG;AAChD,mCAAuB;AACvB;UACF;AAIF,cAAM,WAAW,OAAO,KAAK,YAAY,QAAQ,EAAE,EAAE,YAAW,GAC1D,oBAAoB,iBAAiB,OAAO,CAAC,cACjD,SAAS,SAAS,UAAU,YAAW,CAAE,CAAC;AAI5C,YAAI,CAAC,wBAAwB,kBAAkB,WAAW;AACxD;AAIF,YAAI,gBAAgB,KAAK,mBACvB,UACA,MACA,sBACA,gBAAgB;AAElB,YAAI,CAAC;AACH;AAIF,wBAAgB,YAAY,aAAa,GAIpC,iBAAiB,IAAI,aAAa,IAG5B,qBAAqB,IAAI,aAAa,MAAM,oBACrD,cACE,iDAAiD,aAAa,KAC9D;UACE,UAAU,qBAAqB,IAAI,aAAa,CAAE;UAClD,aAAa,gBAAgB;UAC7B;SACD,KATH,iBAAiB,IAAI,eAAe,CAAA,CAAE,GACtC,qBAAqB,IAAI,eAAe,gBAAgB;AAY1D,cAAM,UAAU,iBAAiB,IAAI,aAAa;AAGlD,YAAI,eAAe;AACnB,YAAI,YAAY;AACd,cAAI;AACF,2BAAe,eAAe,uBAAuB,QAAQ;UAC/D,SAAS,OAAO;AAEd,kBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,0BACE,0DACA;cACE,SAAS,QAAQ;cACjB,UAAU,QAAQ;cAClB;aACD;UAGL;AAIF,gBAAQ,KAAK;UACX,YAAY;UACZ,eAAe;UACf,kBAAkB;SACnB;MACH;AAGA,YAAM,aAAmC,CAAA,GAGnC,eAAe,MAAM,KAAK,iBAAiB,QAAO,CAAE;AAC1D,iBAAW,CAAC,eAAe,OAAO,KAAK,cAAc;AAEnD,cAAM,kBAAkB,oBAAI,IAAG;AAQ/B,mBAAW,UAAU,SAAS;AAC5B,UAAK,gBAAgB,IAAI,OAAO,UAAU,KACxC,gBAAgB,IAAI,OAAO,YAAY;YACrC,gBAAgB,oBAAI,IAAG;YACvB,kBAAkB,oBAAI,IAAG;WAC1B;AAGH,gBAAM,QAAQ,gBAAgB,IAAI,OAAO,UAAU;AACnD,UAAI,OAAO,iBACT,MAAM,eAAe,IAAI,OAAO,aAAa,GAE/C,OAAO,iBAAiB,QAAQ,CAAC,OAAO,MAAM,iBAAiB,IAAI,EAAE,CAAC;QACxE;AAGA,YAAI,yBAAyB,MAAM,KAAK,gBAAgB,QAAO,CAAE;AAYjE,YATA,yBAAyB,uBAAuB,OAAO,CAAC,CAAC,UAAU,MAAK;AAEtE,gBAAM,gBAAgB;YACpB,aAAa;;AAEf,iBAAO,CAAC,KAAK,WAAW,aAAa;QACvC,CAAC,GAGG,uBAAuB,WAAW;AACpC;AAGF,cAAM,eAAe,uBAAuB,IAC1C,CAAC,CAAC,YAAY,QAAQ,OAAO;UAC3B,aAAa;UACb,gBAAgB,MAAM,KAAK,SAAS,cAAc,EAAE,KAAK,IAAI,KAAK;UAClE,mBAAmB,MAAM,KAAK,SAAS,gBAAgB;UACvD;AAGJ,mBAAW,KAAK;UACd,iBAAiB;UACjB,gBAAgB;UAChB,eAAe;UACf,YAAY;UACZ,OAAO,iBAAiB,aAAa,MAAM;SAC5C;MACH;AAMA,YAAM,sBAAsB,CAAC,iBAAiB,iBAAiB,GACzD,wBAAwB,oBAAI,IAAG;AAErC,UAAI;AACF,cAAM,eAAe;;;;;;;;mBAQV,YAAY,GAAG,CAAC;WAGrB,eAAe,eAAe,MAAM,MAAM,WAAW,cAAc,WAAW,CAAC;AACrF,mBAAW,QAAQ,cAAc;AAC/B,cAAI,CAAC,KAAK;AAAW;AAErB,gBAAM,UAAU,eAAe,uBAAuB,KAAK,SAAS,GAI9D,aAAa,WAAW,KAAK,OAAO,IAAI,oBAAoB;AAClE,UAAK,sBAAsB,IAAI,UAAU,KACvC,sBAAsB,IAAI,YAAY,oBAAI,IAAG,CAAE,GAEjD,sBAAsB,IAAI,UAAU,EAAG,IAAI,OAAO;QACpD;MACF,QAAQ;MAER;AAEA,iBAAW,eAAe,qBAAqB;AAC7C,cAAM,cAAc,sBAAsB,IAAI,WAAW;AACzD,YAAI,CAAC,eAAe,YAAY,SAAS;AAAG;AAE5C,cAAM,gBAAgB,gBAAgB,kBAAkB,iBAAiB,kBACnE,eAAe,MAAM,KAAK,WAAW,EACxC,OAAO,CAAC,MAAM,CAAC,KAAK,WAAW,EAAE,aAAa,EAAC,CAAE,CAAC,EAClD,IAAI,CAAC,OAAO;UACX,aAAa;UACb,gBAAgB;UAChB,mBAAmB,CAAA;UACnB;AAEJ,QAAI,aAAa,SAAS,KACxB,WAAW,KAAK;UACd,iBAAiB;UACjB,gBAAgB;UAChB,eAAe;UACf,YAAY;UACZ,OAAO,iBAAiB,aAAa,MAAM,iCAAiC,aAAa;SAC1F;MAEL;AAEA,aAAO;IACT;AACE,aAAO,MAAK;IACd;EACF;;;;;;EAOQ,mBACN,UACA,MACA,gBACA,kBAAyB;AAIzB,QAAI,oBAAoB,kBAElB,KAAK,aAAa,kBAAkB,cAAc;AACpD,aAAO;AAKX,QAAI,gBAAgB;AAElB,YAAM,QAAQ,eAAe,MAAM,SAAS;AAC5C,UAAI;AACF,eAAO,MAAM,CAAC;IAElB;AAGA,UAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,QAAI,SAAS,SAAS,WAAW,KAAK,SAAS,SAAS,QAAQ,GAAG;AACjE,YAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,UAAI,SAAS,MAAM,CAAC;AAClB,eAAO,MAAM,CAAC;IAElB;AAGA,UAAM,WAAW,OAAO,KAAK,SAAS,KAAK,MAAM,EAAE;AAInD,QAAI,UAAU;AACZ,YAAM,cAAc,KAAK,OAAO,eAAe,QAAQ;AAOvD,UALI,eAAe,YAAY,aAAa,SAAS,YAAY,aAAa,iBAK1E,SAAS,YAAW,EAAG,SAAS,UAAU,KAC1C,SAAS,YAAW,EAAG,SAAS,YAAY,KAC5C,SAAS,YAAW,EAAG,SAAS,WAAW;AAC7C,eAAO;IAEX;EAIF;;;;;;;;;;;;;;;;EAiBA,MAAM,QACJ,aACA,eACA,OACA,SAAiC;AAEjC,WAAO,KAAK,cAAc,aAAa,eAAe,OAAO,WAAW,OAAO;EACjF;;;;;;;;;;;;;;;;EAiBA,MAAM,QACJ,aACA,eACA,OACA,SAAiC;AAEjC,WAAO,KAAK,cAAc,aAAa,eAAe,OAAO,YAAY,OAAO;EAClF;;;;;;;;;EAUQ,MAAM,mBACZ,YACA,aACA,eACA,WACA,OACA,cAAoB;AAGpB,UAAM,SAAS,cAAc,QAAQ,MAAM,IAAI,GACzC,QAAQ,KAAK,IAAI,QAAQ,IAAI,GAAG,GAChC,SAAS,cAAc,YACzB,oEAAoE,MAAM,uBAAuB,MAAM,0GAA0G,KAAK,KACtN,oEAAoE,MAAM,uBAAuB,MAAM,0GAA0G,KAAK;AAE1N,IAAI,QAAQ,KACV,cACE,gCAAgC,KAAK,gDACrC,CAAC,wEAAwE,CAAC;AAM9E,UAAM,SAAS,IAAI,YAAW;AAC9B,QAAI;AACF,aAAO,MAAM,UAAU;AACvB,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW,EAAE,MAAM,UAAU,QAAO,CAAE;AAEnD,YAAM,gBAAgB,MAAM,IAAI,SAAS,MAAM,EAAE,WAAW,QAAQ,WAAW,GAEzE,OAAiB,cAAc;AAErC,aAD4B,cAAc,KAC5B,IAAI,CAAC,QAAO;AACxB,cAAM,MAA+B,CAAA;AACrC,oBAAK,QAAQ,CAAC,KAAK,MAAK;AAEtB,gBAAM,MAAM,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI;AACtE,cAAI,GAAG,IAAI,IAAI,CAAC;QAClB,CAAC,GACM,mBAAmB,KAAK,YAAY;MAC7C,CAAC;IACH,SAAS,OAAO;AACd,YAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,2BACE,kEACA;QACE,UAAU,QAAQ;QAClB;OACD,GAEI,CAAA;IACT;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;EAUQ,MAAM,cACZ,aACA,eACA,OACA,WACA,SAAiC;AAGjC,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAKvE,UAAM,eAAe,WAAW,KAAK,GAE/B,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAED,YAAM,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB;AAG9E,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAM,MAAM,UAAU;UAC5B,eAAe;UACf;UACA,OAAO;UACP,SAAS;SACV;MACH,SAAS,OAAO;AACd,cAAI,iBAAiB,gBAAgB,aAAa,KAAK,MAAM,MAAM,IAC3D,IAAI,SACR,uCAAuC,aAAa,IACpD,cAAc,WACd;UACE,GAAI,MAAM,eAAe,CAAA;UACzB;SACD,IAGC;MACR;AAEA,UAAI,CAAC;AAGH,eAAO,KAAK,mBAAmB,UAAU,aAAa,aAAa,eAAe,WAAW,cAAc,qBAAqB;AAGlI,YAAM,QAAQ,MAAM,OACd,QAAQ,MAAM;AAGpB,UAAI,MAAM,UAAU,aAAa,MAAM,SAAS;AAC9C,YAAI;AACF,gBAAM,QAAQ,MAAM,MAAM,YACxB,MAAM,IAAI,CAAC,MAAM,OAAO,EAAE,kBAAkB,EAAE,CAAC,GAC/C,WAAW;AAEb,qBAAW,QAAQ;AACjB,iBAAK,cAAc,MAAM,IAAI,OAAO,KAAK,kBAAkB,EAAE,CAAC;QAElE,SAAS,OAAO;AACd,gBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,wBAAc,qDAAqD,CAAC,UAAU,QAAQ,EAAE,CAAC;QAC3F;AAMF,YAAM,iBAAiB,CAAC,SAAS,GADT,KAAK,OAAO,aAAY,EACI,OAAO,CAAC,MAAM,MAAM,OAAO,CAAC,GAG1E,iBAAkC,CAAA;AAExC,iBAAW,QAAQ,OAAO;AACxB,cAAM,oBAAoB,OAAO,KAAK,kBAAkB,KAAK,MAAM,EAAE;AAGrE,YAAI,gBAAgB,mBAAmB,MAAM,qBAAqB;AAGlE,cAAM,YAAY,OAAO,KAAK,SAAU,WAAW,KAAK,QAAQ,GAC1D,WAAW,kBACf,WACA,OACA,mBACA,cAAc;AAIhB,sBAAc,YAAY,UAE1B,eAAe,KAAK,aAAa;MACnC;AAEA,aAAO;IACT;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;;;EAYA,WAAW,MAAkB;AAQ3B,UAAM,aAAa,KAAK,cAAc,CAAA,GAChC,WAAW,OAAO,KAAK,QAAQ,KAAK,MAAM,EAAE;AAIlD,QAAI,aAAa;AACjB,QAAI,OAAO,WAAW,UAAW;AAC/B,mBAAa,WAAW,OAAO,YAAW;SACrC;AAGL,YAAM,YAFK,OAAO,KAAK,kBAAkB,EAAE,EACpB,MAAM,sBAAsB,IACnB,CAAC,KAAK;AACtC,MAAI,aAAc,mBAAyC,SAAS,SAAS,MAC3E,aAAa;IAEjB;AAGA,UAAM,WAAW,OAAO,WAAW,QAAS,WACxC,WAAW,OACV,OAAO,KAAK,QAAS,YAAY,KAAK,KAAK,WAAW,GAAG,IAAI,KAAK,OAAO,KAExE,UAAU,OAAO,WAAW,gBAAgB,EAAE,GAC9C,aAAa,KAAK,aAAa,IAE/B,eAAe,OAAO,WAAW,UAAU,WAAW,iBAAiB,WAAW,gBAAgB,EAAE;AAO1G,WAAO;MACL,IALc,cAAc,eAC1B,GAAG,UAAU,IAAI,YAAY,KAC5B,YAAY,KAAK,MAAM;MAI1B,aAAa;MACb,WAAW;MACX;MACA,aAAa;MACb,eAAe;;EAEnB;;;;;;;;;EAUA,MAAM,SAAS,aAAqB,UAAqD,CAAA,GAAE;AACzF,UAAM,wBAAwB,QAAQ,gBAAgB,aAChD,QAAQ,eAAe,KAAK,OAAO,cAAa,CAAE;AACxD,QAAI,MAAM,WAAW;AACnB,YAAM,IAAI,SAAS,OAAO,KAAK,IAAI,yCAAyC,cAAc,MAAM;QAC9F;OACD;AAGH,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,YAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,KAAK,OAAO,cAAa,GAAI,GAAG,cAAc,KAAK,GAAG,GAAG,OAAO,KAAK,WAAW,CAAC,CAAC,GACtG,UAAU,QAAQ,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;AAC1D,UAAI,QAAQ;AACV,cAAM,IAAI,SAAS,gBAAgB,QAAQ,SAAS,IAAI,MAAM,EAAE,KAAK,QAAQ,KAAK,IAAI,CAAC,IAAI,cAAc,MAAM;UAC7G,4CAA4C,CAAC,GAAG,KAAK,EAAE,KAAI,EAAG,KAAK,IAAI,CAAC;SACzE;IAEL;AAEA,UAAM,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SAAS,wBAAwB,WAAW,IAAI,cAAc,WAAW;QACjF;QACA;OACD;AAEH,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SAAS,8BAA8B,cAAc,WAAW;QACxE;OACD;AAGH,UAAM,SAAS,IAAI,YAAW;AAC9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW,GAClC,MAAM,OAAO,WAAW,EAAE,MAAM,UAAU,SAAS,MAAM,cAAa,EAAE,CAAE;AAC1E,YAAM,QAAQ,IAAI,SAAS,MAAM,GAC3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB;AAC9E,aAAO,MAAM,cAAc,aAAa,OAAO,WAAW,GAAG,OAAO,KAAK,MAAM;QAC7E,YAAY;QACZ,GAAG;OACJ;IACH;AACE,aAAO,MAAK;IACd;EACF;;EAGA,mBAAgB;AACd,UAAM,eAAe,KAAK,OACvB,cAAa,EACb,IAAI,CAAC,UAAU,KAAK,OAAO,eAAe,KAAK,CAAC,EAChD,OAAO,CAAC,MAAkC,CAAC,CAAC,CAAC,GAC1C,eAAe,KAAK,OACvB,aAAY,EACZ,IAAI,CAAC,SAAS,KAAK,OAAO,eAAe,IAAI,CAAC,EAC9C,OAAO,CAAC,MAAkC,CAAC,CAAC,CAAC;AAChD,WAAO,qBAAqB,eAAe,KAAK,OAAO,cAAa,CAAE,GAAG,cAAc,YAAY;EACrG;;;;;;;;EASA,MAAM,QAAQ,aAAqB,UAAoD,CAAA,GAAE;AACvF,UAAM,wBAAwB,QAAQ,gBAAgB,aAChD,SAAS,KAAK,iBAAgB;AACpC,QAAI,OAAO,MAAM,WAAW;AAC1B,YAAM,IAAI,SAAS,OAAO,KAAK,IAAI,yCAAyC,cAAc,IAAI;AAEhG,UAAM,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SAAS,wBAAwB,WAAW,IAAI,cAAc,WAAW,CAAC,8CAA8C,CAAC;AAErI,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,SAAS,8BAA8B,cAAc,WAAW;QACxE;OACD;AAEH,UAAM,SAAS,IAAI,YAAW;AAC9B,QAAI;AACF,aAAO,MAAM,OAAO,SAAS,WAAW,GACxC,MAAM,OAAO,WAAW,EAAE,MAAM,UAAU,SAAS,MAAM,cAAa,EAAE,CAAE;AAC1E,YAAM,QAAQ,IAAI,SAAS,QAAQ,EAAE,SAAS,wBAAuB,CAAE,GACjE,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB;AAC9E,aAAO,MAAM,kBAAkB,aAAa,OAAO,WAAW,GAAG,QAAQ,KAAK,MAAM;QAClF,YAAY;QACZ,GAAG;OACJ;IACH;AACE,aAAO,MAAK;IACd;EACF;;;;;;;;;;;;EAaA,MAAM,OACJ,aACA,SAAgD;AAGhD,UAAM,wBAAwB,SAAS,gBAAgB,aAGjD,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,cAAc,sBAAqB,CAAE;AACrF,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,SACR,wBAAwB,WAAW,IACnC,cAAc,WACd;QACE;QACA;OACD;AAKL,UAAM,YAAY,OAAO;AACzB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,SACR,8BACA,cAAc,WACd,CAAC,iEAAiE,CAAC;AAKvE,UAAM,eAAe,KAAK,OAAO,eAAe,OAAO;AACvD,QAAI,CAAC;AACH,YAAM,IAAI,SACR,uCACA,cAAc,YACd,CAAC,8DAA8D,CAAC;AAIpE,UAAM,SAAS,IAAI,YAAW;AAE9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW;AAElC,YAAM,UAAU,MAAM,cAAa;AACnC,YAAM,OAAO,WAAW;QACtB,MAAM;QACN;OACD;AAED,YAAM,QAAQ,IAAI,SAAS,MAAM,GAG3B,cAAc,MAAM,KAAK,mBAAmB,OAAO,qBAAqB,GAaxE,UAVW,MAAM,KAAK,YAAY,OAAO,aAAa,aAAa,SAAS,GAM/E,OAAO,CAAC,MAAM,uBAAuB,KAAK,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,EACzE,OAAO,CAAC,MAAM,kBAAkB,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,EAI/D,IAAI,CAAC,SAAS,KAAK,WAAW,IAAI,CAAC,EACnC,OAAO,CAAC,MAAM,EAAE,WAAW,GAGxB,SAAS,IAAI,aAAY,GACzB,YAAY,OAAO,YACnB,eAAe,KAAK;AAG1B,aAFe,MAAM,OAAO,cAAc,aAAa,QAAQ,SAAS,cAAc,SAAS;IAGjG;AACE,aAAO,MAAK;IACd;EACF;;AAIF,MAAM,qBACJ;AAcF,eAAsB,gBACpB,cACA,aAAmB;AAEnB,QAAM,OAAO,KAAK,KAAK,cAAc,YAAY;AACjD,MAAI,OAAO,IACP,UAAU;AACd,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,OAAO,GACnC,UAAU;EACZ,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS;AAAU,YAAM;EAChE;AACA,QAAM,QAAQ,KACX,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAI,CAAE,EACzB,OAAO,CAAC,SAAS,QAAQ,CAAC,KAAK,WAAW,GAAG,CAAC,GAC3C,SAAS,CAAC,SAAiB,KAAK,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE,GACtE,SAAS,IAAI,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,GAAG,CAAC,EAAE,IAAI,MAAM,CAAC,GAG1E,UAAU,IAAI,IAAI,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GACnG,QAAkB,CAAA;AACxB,aAAW,OAAO,aAAa;AAC7B,UAAM,WAAW,KAAK,SAAS,cAAc,KAAK,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AAClG,IAAI,CAAC,YAAY,SAAS,WAAW,IAAI,KAAK,KAAK,WAAW,QAAQ,KAClE,CAAC,OAAO,IAAI,QAAQ,KAAK,CAAC,QAAQ,IAAI,QAAQ,KAAG,MAAM,KAAK,IAAI,QAAQ,GAAG;EACjF;AACA,MAAI,MAAM,WAAW;AAAG,WAAO,EAAE,MAAM,MAAM,OAAO,SAAS,GAAK;AAElE,QAAM,YAAY,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS;CAAI,IAAI;IAAO;;GAClE,UAAU,KAAK,SAAS,kBAAkB,IAAI,KAAK,GAAG,kBAAkB;;AAC9E,eAAM,UAAU,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,KAAK;CAAI,CAAC;CAAI,GACnE,EAAE,MAAM,MAAM,OAAO,QAAO;AACrC;",
|
|
6
6
|
"names": ["meta"]
|
|
7
7
|
}
|