@neat.is/core 0.4.26-dev.20260703 → 0.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2LNICOVU.js → chunk-A3322JYS.js} +29 -2
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-4OR4RQEO.js → chunk-BIY46Q6U.js} +13 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-WZYH5DVG.js → chunk-QM6BMPVJ.js} +419 -218
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-C5NCCKPZ.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-S7TDPQDD.js → chunk-XV4D7A3Z.js} +105 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +732 -456
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +587 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +600 -286
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-LFYZDSK3.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +410 -182
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-2LNICOVU.js.map +0 -1
- package/dist/chunk-4OR4RQEO.js.map +0 -1
- package/dist/chunk-S7TDPQDD.js.map +0 -1
- package/dist/chunk-WZYH5DVG.js.map +0 -1
- /package/dist/{chunk-C5NCCKPZ.js.map → chunk-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-LFYZDSK3.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts","../src/compat.ts","../compat.json","../src/traverse.ts","../src/ingest.ts","../src/policy.ts","../src/events.ts","../src/extract/errors.ts","../src/extract/services.ts","../src/extract/shared.ts","../src/extract/python.ts","../src/extract/owners.ts","../src/extract/aliases.ts","../src/extract/calls/shared.ts","../src/extract/files.ts","../src/extract/imports.ts","../src/extract/databases/index.ts","../src/extract/databases/db-config-yaml.ts","../src/extract/databases/dotenv.ts","../src/extract/databases/shared.ts","../src/extract/databases/prisma.ts","../src/extract/databases/drizzle.ts","../src/extract/databases/knex.ts","../src/extract/databases/ormconfig.ts","../src/extract/databases/typeorm.ts","../src/extract/databases/sequelize.ts","../src/extract/databases/docker-compose.ts","../src/extract/configs.ts","../src/extract/routes.ts","../src/extract/proto.ts","../src/extract/calls/index.ts","../src/extract/calls/http.ts","../src/extract/calls/route-match.ts","../src/extract/calls/kafka.ts","../src/extract/calls/redis.ts","../src/extract/calls/aws.ts","../src/extract/calls/grpc.ts","../src/extract/calls/supabase.ts","../src/extract/infra/docker-compose.ts","../src/extract/infra/shared.ts","../src/extract/infra/dockerfile.ts","../src/extract/infra/terraform.ts","../src/extract/infra/k8s.ts","../src/extract/infra/index.ts","../src/extract/index.ts","../src/extract/retire.ts","../src/divergences.ts","../src/persist.ts","../src/diff.ts","../src/projects.ts","../src/registry.ts","../src/api.ts","../src/extend/index.ts","../src/installers/package-manager.ts","../src/streaming.ts"],"sourcesContent":["import GraphDefault from 'graphology'\nimport type { MultiDirectedGraph as MDGType } from 'graphology'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\n\n// graphology ships as a CJS bundle that does `module.exports = Graph` with\n// the other constructors attached as properties (`Graph.MultiDirectedGraph =\n// ...`). cjs-module-lexer can't see through that attachment, so a named\n// import like `import { MultiDirectedGraph } from 'graphology'` fails under\n// strict Node ESM (Node 22+ via tsx in particular). Pull the constructor off\n// the default export instead — same shape under tsx and tsup-bundled output.\ntype MultiDirectedGraphCtor = typeof MDGType\nconst MultiDirectedGraph: MultiDirectedGraphCtor = (\n GraphDefault as unknown as { MultiDirectedGraph: MultiDirectedGraphCtor }\n).MultiDirectedGraph\n\n// Multi because two nodes can have edges of different types simultaneously\n// (e.g. CALLS and DEPENDS_ON between the same pair of services).\nexport type NeatGraph = MDGType<GraphNode, GraphEdge>\n\nexport const DEFAULT_PROJECT = 'default'\n\n// One graph per project. The map is the source of truth; getGraph() with no\n// arg or with 'default' hits the legacy single-project path so existing\n// callers keep working byte-for-byte (ADR-026).\nconst graphs = new Map<string, NeatGraph>()\n\nfunction makeGraph(): NeatGraph {\n return new MultiDirectedGraph<GraphNode, GraphEdge>({ allowSelfLoops: false })\n}\n\nexport function getGraph(project: string = DEFAULT_PROJECT): NeatGraph {\n let g = graphs.get(project)\n if (!g) {\n g = makeGraph()\n graphs.set(project, g)\n }\n return g\n}\n\nexport function hasProject(project: string): boolean {\n return graphs.has(project)\n}\n\nexport function listProjects(): string[] {\n return [...graphs.keys()].sort()\n}\n\n// Reset a single project, or all of them when the arg is omitted. Tests use\n// the no-arg form between cases; runtime never calls it.\nexport function resetGraph(project?: string): void {\n if (project === undefined) {\n graphs.clear()\n return\n }\n graphs.delete(project)\n}\n","import { promises as fs } from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport semver from 'semver'\nimport compatData from '../compat.json' with { type: 'json' }\n\nexport interface CompatibilityResult {\n compatible: boolean\n reason?: string\n minDriverVersion?: string\n}\n\nexport interface CompatPair {\n kind?: 'driver-engine'\n driver: string\n engine: string\n minDriverVersion: string\n // The driver constraint only kicks in once the engine is at this major or higher.\n // Older engines (e.g. PostgreSQL 13) accept the older driver fine.\n minEngineVersion?: string\n reason: string\n}\n\nexport interface NodeEngineConstraint {\n kind?: 'node-engine'\n package: string\n packageMinVersion?: string\n minNodeVersion: string\n reason: string\n}\n\nexport interface PackageConflict {\n kind?: 'package-conflict'\n package: string\n packageMinVersion?: string\n requires: { name: string; minVersion: string }\n reason: string\n}\n\nexport interface DeprecatedApi {\n kind?: 'deprecated-api'\n package: string\n packageMaxVersion?: string\n reason: string\n}\n\nexport interface CompatMatrix {\n pairs: CompatPair[]\n nodeEngineConstraints?: NodeEngineConstraint[]\n packageConflicts?: PackageConflict[]\n deprecatedApis?: DeprecatedApi[]\n}\n\nconst bundledMatrix = compatData as CompatMatrix\nlet mergedMatrix: CompatMatrix | null = null\nlet remoteLoadAttempted = false\n\nconst REMOTE_CACHE_DIR = path.join(os.homedir(), '.neat')\nconst REMOTE_CACHE_PATH = path.join(REMOTE_CACHE_DIR, 'compat-cache.json')\nconst REMOTE_TTL_MS = 24 * 60 * 60 * 1000\n\ninterface RemoteCacheFile {\n fetchedAt: string\n url: string\n matrix: CompatMatrix\n}\n\n// Engines like Postgres/MySQL only carry a major in the version field, so semver\n// won't always parse them cleanly. Compare as integers when both sides look like\n// majors; otherwise fall back to semver.coerce.\nfunction engineMeetsThreshold(engineVersion: string, threshold: string): boolean {\n const e = parseInt(engineVersion, 10)\n const t = parseInt(threshold, 10)\n if (Number.isFinite(e) && Number.isFinite(t)) return e >= t\n\n const ec = semver.coerce(engineVersion)\n const tc = semver.coerce(threshold)\n if (ec && tc) return semver.gte(ec, tc)\n\n return false\n}\n\nexport function checkCompatibility(\n driver: string,\n driverVersion: string,\n engine: string,\n engineVersion: string,\n): CompatibilityResult {\n const matrix = currentMatrix()\n const pair = matrix.pairs.find((p) => p.driver === driver && p.engine === engine)\n if (!pair) return { compatible: true }\n\n if (pair.minEngineVersion && !engineMeetsThreshold(engineVersion, pair.minEngineVersion)) {\n return { compatible: true }\n }\n\n const driverCoerced = semver.coerce(driverVersion)\n if (!driverCoerced) return { compatible: true }\n\n if (semver.lt(driverCoerced, pair.minDriverVersion)) {\n return {\n compatible: false,\n reason: pair.reason,\n minDriverVersion: pair.minDriverVersion,\n }\n }\n\n return { compatible: true }\n}\n\nexport interface NodeEngineCheck {\n compatible: boolean\n reason?: string\n requiredNodeVersion?: string\n}\n\n// True when `serviceNodeRange` (a service's `engines.node`) is guaranteed to\n// admit `requiredNodeVersion`. We use a permissive semver compare via `coerce`\n// — exact ranges like \">=20\" parse fine, exotic ones like \"^20 || ^22\" pass as\n// long as semver can resolve them. If the range can't be parsed at all, we\n// don't claim a conflict — under-flag rather than over-flag.\nfunction rangeAdmitsVersion(serviceNodeRange: string, requiredNodeVersion: string): boolean {\n try {\n const required = semver.coerce(requiredNodeVersion)\n if (!required) return true\n // Is every version that satisfies the service's range >= required? If yes,\n // the service guarantees the requirement; if not, there's at least one\n // admissible Node version that won't satisfy the dep — that's the\n // conflict.\n return semver.subset(serviceNodeRange, `>=${required.version}`, {\n includePrerelease: false,\n })\n } catch {\n return true\n }\n}\n\nexport function checkNodeEngineConstraint(\n constraint: NodeEngineConstraint,\n declaredPackageVersion: string | undefined,\n serviceNodeRange: string | undefined,\n): NodeEngineCheck {\n if (constraint.packageMinVersion && declaredPackageVersion) {\n const v = semver.coerce(declaredPackageVersion)\n if (v && semver.lt(v, constraint.packageMinVersion)) {\n return { compatible: true }\n }\n }\n if (!serviceNodeRange) {\n return { compatible: true }\n }\n if (rangeAdmitsVersion(serviceNodeRange, constraint.minNodeVersion)) {\n return { compatible: true }\n }\n return {\n compatible: false,\n reason: constraint.reason,\n requiredNodeVersion: constraint.minNodeVersion,\n }\n}\n\nexport interface PackageConflictCheck {\n compatible: boolean\n reason?: string\n requires?: { name: string; minVersion: string }\n foundVersion?: string\n}\n\nexport function checkPackageConflict(\n conflict: PackageConflict,\n declaredPackageVersion: string | undefined,\n declaredRequiredVersion: string | undefined,\n): PackageConflictCheck {\n if (!declaredPackageVersion) return { compatible: true }\n if (conflict.packageMinVersion) {\n const v = semver.coerce(declaredPackageVersion)\n if (v && semver.lt(v, conflict.packageMinVersion)) {\n return { compatible: true }\n }\n }\n if (!declaredRequiredVersion) {\n return {\n compatible: false,\n reason: conflict.reason,\n requires: conflict.requires,\n }\n }\n const requiredCoerced = semver.coerce(declaredRequiredVersion)\n if (!requiredCoerced) return { compatible: true }\n if (semver.lt(requiredCoerced, conflict.requires.minVersion)) {\n return {\n compatible: false,\n reason: conflict.reason,\n requires: conflict.requires,\n foundVersion: declaredRequiredVersion,\n }\n }\n return { compatible: true }\n}\n\nexport function checkDeprecatedApi(\n rule: DeprecatedApi,\n declaredVersion: string | undefined,\n): { compatible: boolean; reason?: string } {\n if (declaredVersion === undefined) return { compatible: true }\n if (rule.packageMaxVersion) {\n const v = semver.coerce(declaredVersion)\n const max = semver.coerce(rule.packageMaxVersion)\n if (v && max && semver.gt(v, max)) return { compatible: true }\n }\n return { compatible: false, reason: rule.reason }\n}\n\nfunction currentMatrix(): CompatMatrix {\n return mergedMatrix ?? bundledMatrix\n}\n\nfunction mergeMatrices(a: CompatMatrix, b: CompatMatrix): CompatMatrix {\n return {\n pairs: [...a.pairs, ...(b.pairs ?? [])],\n nodeEngineConstraints: [\n ...(a.nodeEngineConstraints ?? []),\n ...(b.nodeEngineConstraints ?? []),\n ],\n packageConflicts: [...(a.packageConflicts ?? []), ...(b.packageConflicts ?? [])],\n deprecatedApis: [...(a.deprecatedApis ?? []), ...(b.deprecatedApis ?? [])],\n }\n}\n\nasync function readRemoteCache(url: string): Promise<CompatMatrix | null> {\n try {\n const raw = await fs.readFile(REMOTE_CACHE_PATH, 'utf8')\n const parsed = JSON.parse(raw) as RemoteCacheFile\n if (parsed.url !== url) return null\n const age = Date.now() - new Date(parsed.fetchedAt).getTime()\n if (age > REMOTE_TTL_MS) return null\n return parsed.matrix\n } catch {\n return null\n }\n}\n\nasync function writeRemoteCache(url: string, matrix: CompatMatrix): Promise<void> {\n const file: RemoteCacheFile = {\n fetchedAt: new Date().toISOString(),\n url,\n matrix,\n }\n try {\n await fs.mkdir(REMOTE_CACHE_DIR, { recursive: true })\n await fs.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), 'utf8')\n } catch (err) {\n console.warn(`[neat] failed to cache compat matrix: ${(err as Error).message}`)\n }\n}\n\n// Loads the bundled matrix and, if `NEAT_COMPAT_URL` is set, merges in a\n// remote extension. Falls back to a fresh fetch when the on-disk cache is\n// stale (24h TTL) or missing. Returns the merged matrix; subsequent calls are\n// memoised.\n//\n// Async because the fetch happens lazily on first use. Extract phase 2 awaits\n// this before iterating pairs; everything else goes through the sync\n// `currentMatrix()` view, which is fine because by the time CLI / traversal\n// runs, extraction has already loaded.\nexport async function ensureCompatLoaded(): Promise<CompatMatrix> {\n if (mergedMatrix) return mergedMatrix\n if (remoteLoadAttempted) {\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n remoteLoadAttempted = true\n\n const url = process.env.NEAT_COMPAT_URL\n if (!url) {\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n\n const cached = await readRemoteCache(url)\n if (cached) {\n mergedMatrix = mergeMatrices(bundledMatrix, cached)\n return mergedMatrix\n }\n\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)\n const remote = (await res.json()) as CompatMatrix\n await writeRemoteCache(url, remote)\n mergedMatrix = mergeMatrices(bundledMatrix, remote)\n return mergedMatrix\n } catch (err) {\n console.warn(\n `[neat] NEAT_COMPAT_URL fetch failed (${(err as Error).message}); using bundled matrix only`,\n )\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n}\n\n// Reset the merged-matrix memo. Intended for tests so each test starts with a\n// freshly loaded matrix.\nexport function resetCompatMatrix(): void {\n mergedMatrix = null\n remoteLoadAttempted = false\n}\n\nexport function compatPairs(): readonly CompatPair[] {\n return currentMatrix().pairs\n}\n\nexport function nodeEngineConstraints(): readonly NodeEngineConstraint[] {\n return currentMatrix().nodeEngineConstraints ?? []\n}\n\nexport function packageConflicts(): readonly PackageConflict[] {\n return currentMatrix().packageConflicts ?? []\n}\n\nexport function deprecatedApis(): readonly DeprecatedApi[] {\n return currentMatrix().deprecatedApis ?? []\n}\n","{\n \"pairs\": [\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"pg\",\n \"engine\": \"postgresql\",\n \"minDriverVersion\": \"8.0.0\",\n \"minEngineVersion\": \"14\",\n \"reason\": \"PostgreSQL 14+ requires scram-sha-256 auth by default; pg < 8.0.0 only speaks md5.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mysql2\",\n \"engine\": \"mysql\",\n \"minDriverVersion\": \"3.0.0\",\n \"minEngineVersion\": \"8\",\n \"reason\": \"MySQL 8 defaults to caching_sha2_password; mysql2 < 3.0.0 doesn't negotiate it.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mongoose\",\n \"engine\": \"mongodb\",\n \"minDriverVersion\": \"7.0.0\",\n \"minEngineVersion\": \"7\",\n \"reason\": \"MongoDB 7 drops legacy wire-protocol opcodes that mongoose < 7.0.0 still emits.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"psycopg2\",\n \"engine\": \"postgresql\",\n \"minDriverVersion\": \"2.9.0\",\n \"minEngineVersion\": \"14\",\n \"reason\": \"PostgreSQL 14+ requires scram-sha-256 auth by default; psycopg2 < 2.9.0 only speaks md5.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"pymongo\",\n \"engine\": \"mongodb\",\n \"minDriverVersion\": \"4.0.0\",\n \"minEngineVersion\": \"7\",\n \"reason\": \"MongoDB 7 drops legacy wire-protocol opcodes that pymongo < 4.0.0 still emits.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mysql-connector-python\",\n \"engine\": \"mysql\",\n \"minDriverVersion\": \"8.0.0\",\n \"minEngineVersion\": \"8\",\n \"reason\": \"MySQL 8 defaults to caching_sha2_password; mysql-connector-python < 8.0.0 doesn't negotiate it.\"\n }\n ],\n \"nodeEngineConstraints\": [\n {\n \"kind\": \"node-engine\",\n \"package\": \"vitest\",\n \"packageMinVersion\": \"2.0.0\",\n \"minNodeVersion\": \"18.0.0\",\n \"reason\": \"vitest >= 2.0 drops Node 16 support; requires Node 18+.\"\n },\n {\n \"kind\": \"node-engine\",\n \"package\": \"next\",\n \"packageMinVersion\": \"14.0.0\",\n \"minNodeVersion\": \"18.17.0\",\n \"reason\": \"Next 14+ requires Node 18.17+ (uses APIs introduced in that minor).\"\n },\n {\n \"kind\": \"node-engine\",\n \"package\": \"@modelcontextprotocol/sdk\",\n \"packageMinVersion\": \"1.0.0\",\n \"minNodeVersion\": \"18.0.0\",\n \"reason\": \"@modelcontextprotocol/sdk >= 1 requires Node 18+ (web-streams polyfill removed).\"\n }\n ],\n \"packageConflicts\": [\n {\n \"kind\": \"package-conflict\",\n \"package\": \"@tanstack/react-query\",\n \"packageMinVersion\": \"5.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.0.0\"\n },\n \"reason\": \"@tanstack/react-query 5+ uses useSyncExternalStore — only available in React 18+.\"\n },\n {\n \"kind\": \"package-conflict\",\n \"package\": \"react-router-dom\",\n \"packageMinVersion\": \"7.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.0.0\"\n },\n \"reason\": \"react-router-dom 7+ requires React 18+.\"\n },\n {\n \"kind\": \"package-conflict\",\n \"package\": \"next\",\n \"packageMinVersion\": \"14.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.2.0\"\n },\n \"reason\": \"Next.js 14+ requires React 18.2+.\"\n }\n ],\n \"deprecatedApis\": [\n {\n \"kind\": \"deprecated-api\",\n \"package\": \"request\",\n \"packageMaxVersion\": \"2.88.2\",\n \"reason\": \"request is deprecated; use undici, node-fetch, or axios instead.\"\n },\n {\n \"kind\": \"deprecated-api\",\n \"package\": \"node-uuid\",\n \"reason\": \"node-uuid is deprecated; use the `uuid` package.\"\n }\n ]\n}\n","import type {\n BlastRadiusAffectedNode,\n BlastRadiusResult,\n DatabaseNode,\n ErrorEvent,\n GraphEdge,\n GraphNode,\n ObservedDependenciesResult,\n RootCauseResult,\n ServiceNode,\n TransitiveDependenciesResult,\n TransitiveDependency,\n} from '@neat.is/types'\nimport {\n BlastRadiusResultSchema,\n EdgeType,\n NodeType,\n ObservedDependenciesResultSchema,\n PROV_RANK,\n Provenance,\n RootCauseResultSchema,\n TransitiveDependenciesResultSchema,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport {\n checkCompatibility,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n nodeEngineConstraints,\n packageConflicts,\n} from './compat.js'\n\n// Contract anchors (see /docs/contracts.md + docs/contracts/provenance.md):\n// * Rule 2 — Coexistence: walk by provenance priority, never collapse edges.\n// * Rule 3 — FrontierNodes terminate traversal — edges to/from FrontierNodes\n// are skipped, not merely deprioritized. If a node's only neighbour is a\n// FrontierNode, traversal stops there. ADR-068 makes node-type the gating\n// property, independent of edge provenance.\n// * Rule 5 — Validate results against RootCauseResultSchema /\n// BlastRadiusResultSchema before returning.\n// * Rule 8 — No demo-name hardcoding: driver/engine identifiers come from\n// node properties + compatPairs(), never literals.\n// * ADR-029 — PROV_RANK is the canonical provenance ranking, imported\n// from @neat.is/types so consumers (traversal, MCP, policies) all agree.\n\nconst ROOT_CAUSE_MAX_DEPTH = 5\nconst BLAST_RADIUS_DEFAULT_DEPTH = 10\n\nfunction isFrontierNode(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.FrontierNode\n}\n\n// Resolve a node on the walk path to the ServiceNode that carries the compat\n// evidence (declared dependencies + node engine). A ServiceNode resolves to\n// itself; a FileNode resolves to its owning service via the inbound\n// `service ──CONTAINS──▶ file` edge (file-awareness.md §2) — in a file-first\n// graph the caller on the path is a FileNode, but the dependency declaration\n// lives on the service that owns it. Anything else has no service to resolve\n// to. Returns the resolved ServiceNode's id + attributes, or null.\nfunction resolveOwningService(\n graph: NeatGraph,\n nodeId: string,\n): { id: string; svc: ServiceNode } | null {\n if (!graph.hasNode(nodeId)) return null\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n if (attrs.type === NodeType.ServiceNode) {\n return { id: nodeId, svc: attrs as ServiceNode }\n }\n if (attrs.type === NodeType.FileNode) {\n for (const edgeId of graph.inboundEdges(nodeId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const owner = graph.getNodeAttributes(e.source) as GraphNode\n if (owner.type === NodeType.ServiceNode) {\n return { id: e.source, svc: owner as ServiceNode }\n }\n }\n }\n return null\n}\n\n// Multiple edges between the same pair coexist by provenance (EXTRACTED next to\n// OBSERVED next to INFERRED). Traversal walks the system as the graph \"sees it\n// best\", so for any neighbour pair we pick the highest-provenance edge.\n// Edges connecting to FrontierNodes are skipped at the node level (ADR-068):\n// FrontierNodes are unresolved peers, traversal terminates at them rather than\n// pretending the path continues into unknown territory.\nfunction bestEdgeBySource(graph: NeatGraph, edgeIds: string[]): Map<string, GraphEdge> {\n const best = new Map<string, GraphEdge>()\n for (const id of edgeIds) {\n const e = graph.getEdgeAttributes(id) as GraphEdge\n if (isFrontierNode(graph, e.source)) continue\n const cur = best.get(e.source)\n if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {\n best.set(e.source, e)\n }\n }\n return best\n}\n\nfunction bestEdgeByTarget(graph: NeatGraph, edgeIds: string[]): Map<string, GraphEdge> {\n const best = new Map<string, GraphEdge>()\n for (const id of edgeIds) {\n const e = graph.getEdgeAttributes(id) as GraphEdge\n if (isFrontierNode(graph, e.target)) continue\n const cur = best.get(e.target)\n if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {\n best.set(e.target, e)\n }\n }\n return best\n}\n\n// Per-edge confidence is provenance × volume × recency × cleanliness.\n// * provenance gives a ceiling: OBSERVED 1.0, INFERRED 0.7, EXTRACTED 0.5,\n// STALE 0.3.\n// * volume: log-scaled span count, saturating quickly so 1 span ≈ 0.55 and\n// ~1k spans ≈ 1.0.\n// * recency: 1.0 within an hour; decays toward 0.5 by 24h, toward 0.3 past.\n// * cleanliness: error rate above ~10% pulls the score down — a flapping\n// edge with thousands of spans shouldn't outrank a clean low-traffic one.\n// Bounded to [0, 1]. Walks of multiple edges multiply per-edge confidences.\nconst PROVENANCE_CEILING: Record<string, number> = {\n OBSERVED: 1.0,\n INFERRED: 0.7,\n EXTRACTED: 0.5,\n STALE: 0.3,\n}\n\nfunction volumeWeight(spanCount: number | undefined): number {\n if (!spanCount || spanCount <= 0) return 0.5\n // log10 saturating around ~1000 spans → ~1.0.\n const w = 0.5 + Math.log10(spanCount + 1) / 3\n return Math.min(1, w)\n}\n\nfunction recencyWeight(ageMs: number | undefined): number {\n if (ageMs === undefined) return 0.8\n const hour = 60 * 60 * 1000\n if (ageMs <= hour) return 1.0\n if (ageMs <= 24 * hour) {\n const t = (ageMs - hour) / (23 * hour)\n return 1.0 - 0.5 * t\n }\n return 0.3\n}\n\nfunction cleanlinessWeight(spanCount: number | undefined, errorCount: number | undefined): number {\n if (!spanCount || spanCount <= 0) return 1\n const rate = (errorCount ?? 0) / spanCount\n if (rate <= 0.01) return 1\n if (rate >= 0.5) return 0.3\n return 1 - rate * 1.4\n}\n\nexport function confidenceForEdge(edge: GraphEdge, now = Date.now()): number {\n const ceiling = PROVENANCE_CEILING[edge.provenance] ?? 0.5\n\n // No runtime signal yet → the provenance ceiling is all we have. This keeps\n // EXTRACTED-only graphs returning the same coarse 0.3/0.5/0.7/1.0 ladder\n // they always have, while letting OBSERVED edges with real OTel data move\n // off the ceiling once ingest starts populating signal counters.\n const spanCount = edge.signal?.spanCount ?? edge.callCount\n const ageMs = edge.signal?.lastObservedAgeMs ?? lastObservedAge(edge, now)\n if (spanCount === undefined && ageMs === undefined && edge.signal === undefined) {\n return ceiling\n }\n\n const v = volumeWeight(spanCount)\n const r = recencyWeight(ageMs)\n const c = cleanlinessWeight(spanCount, edge.signal?.errorCount)\n return Math.max(0, Math.min(1, ceiling * v * r * c))\n}\n\nfunction lastObservedAge(edge: GraphEdge, now: number): number | undefined {\n if (!edge.lastObserved) return undefined\n const t = Date.parse(edge.lastObserved)\n if (!Number.isFinite(t)) return undefined\n return Math.max(0, now - t)\n}\n\n// Path-level confidence is the *product* of per-edge confidences (ADR-036).\n// Each hop is independent evidence and uncertainty compounds — a 3-hop path\n// of edges at confidence 0.8 each gives 0.512, not 0.8. Multiplying punishes\n// long walks accordingly, which is the contract's intent: traversal should\n// surface the cumulative trust the graph actually has, not the weakest link\n// alone.\nfunction confidenceFromMix(edges: GraphEdge[], now = Date.now()): number {\n if (edges.length === 0) return 1.0\n let product = 1\n for (const e of edges) {\n product *= confidenceForEdge(e, now)\n }\n return Math.max(0, Math.min(1, product))\n}\n\ninterface Walk {\n path: string[]\n edges: GraphEdge[]\n}\n\n// DFS along incoming edges from start, depth-bounded. Returns the longest path\n// reachable, picking best-provenance edges per neighbour pair so the walk\n// reflects the system as the graph knows it most reliably.\nfunction longestIncomingWalk(graph: NeatGraph, start: string, maxDepth: number): Walk {\n let best: Walk = { path: [start], edges: [] }\n const visited = new Set<string>([start])\n\n function step(node: string, path: string[], edges: GraphEdge[]): void {\n if (path.length > best.path.length) {\n best = { path: [...path], edges: [...edges] }\n }\n if (path.length - 1 >= maxDepth) return\n\n const incoming = bestEdgeBySource(graph, graph.inboundEdges(node))\n for (const [srcId, edge] of incoming) {\n if (visited.has(srcId)) continue\n visited.add(srcId)\n path.push(srcId)\n edges.push(edge)\n step(srcId, path, edges)\n path.pop()\n edges.pop()\n visited.delete(srcId)\n }\n }\n\n step(start, [start], [])\n return best\n}\n\n// Per-shape match result. Each shape walks the same incoming `walk.path` but\n// looks for a different class of incompatibility. Adding a new shape (e.g. a\n// future ConfigNode \"missing required env var\" rule) is one entry in\n// `rootCauseShapes` plus its match function — no restructure to getRootCause.\ninterface RootCauseMatch {\n rootCauseNode: string\n rootCauseReason: string\n fixRecommendation?: string\n}\n\ntype RootCauseShape = (\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n) => RootCauseMatch | null\n\n// DatabaseNode origin → driver/engine compat (the original v0.1.x behavior,\n// preserved verbatim). The walk ignores non-ServiceNodes; the first upstream\n// service whose declared driver fails compat against the origin DB's\n// (engine, engineVersion) wins.\nfunction databaseRootCauseShape(\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n const targetDb = origin as DatabaseNode\n // Pairs that could possibly hit on this engine — narrowed once outside the\n // walk so we don't re-scan the matrix for every service we visit.\n const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine)\n if (candidatePairs.length === 0) return null\n\n for (const id of walk.path) {\n // The compat carrier is a service: a ServiceNode resolves to itself, a\n // FileNode on the path resolves to its owning service via CONTAINS\n // (file-awareness.md §2). In a file-first graph the caller on the walk is\n // the FileNode that holds the CALLS edge, but the declared driver lives on\n // the service that owns it.\n const owner = resolveOwningService(graph, id)\n if (!owner) continue\n const { id: serviceId, svc } = owner\n const deps = svc.dependencies ?? {}\n for (const pair of candidatePairs) {\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(\n pair.driver,\n declared,\n targetDb.engine,\n targetDb.engineVersion,\n )\n if (!result.compatible) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason ?? 'incompatible driver',\n ...(result.minDriverVersion\n ? {\n fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`,\n }\n : {}),\n }\n }\n }\n }\n return null\n}\n\n// ServiceNode origin → node-engine + package-conflict shapes from compat.ts.\n// The check is over each ServiceNode along the incoming walk (the origin\n// itself + any upstream callers): a node-engine constraint failing against\n// the service's `engines.node`, or a package-conflict where a declared dep\n// requires a peer at a higher version than the service has.\nfunction serviceRootCauseShape(\n graph: NeatGraph,\n _origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n for (const id of walk.path) {\n // ServiceNode → itself; FileNode → owning service via CONTAINS\n // (file-awareness.md §2). The compat evidence (declared deps, node engine)\n // lives on the service, even when the caller on the walk is a file.\n const owner = resolveOwningService(graph, id)\n if (!owner) continue\n const { id: serviceId, svc } = owner\n const deps = svc.dependencies ?? {}\n const serviceNodeEngine = svc.nodeEngine\n\n for (const constraint of nodeEngineConstraints()) {\n const declared = deps[constraint.package]\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine)\n if (!result.compatible && result.reason) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason,\n ...(result.requiredNodeVersion\n ? {\n fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`,\n }\n : {}),\n }\n }\n }\n\n for (const conflict of packageConflicts()) {\n const declared = deps[conflict.package]\n if (!declared) continue\n const requiredDeclared = deps[conflict.requires.name]\n const result = checkPackageConflict(conflict, declared, requiredDeclared)\n if (!result.compatible && result.reason) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason,\n fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`,\n }\n }\n }\n }\n return null\n}\n\n// FileNode origin → resolve the file to its owning service (file-awareness.md\n// §2) and run the service shape. In a file-first graph an error can land on a\n// FileNode (the file that holds the failing CALLS edge); the incompatibility,\n// if any, is still a property of the service that owns the file's declared\n// dependencies. The owning service is folded into the origin's position so the\n// service shape scans it alongside the upstream walk.\nfunction fileRootCauseShape(\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n const owner = resolveOwningService(graph, origin.id)\n if (!owner) return null\n return serviceRootCauseShape(graph, owner.svc, walk)\n}\n\n// Dispatch by origin node type per ADR-037. Origin types not present here\n// (InfraNode, ConfigNode, FrontierNode) cleanly return null — getRootCause\n// needs an explicit shape to know what an \"incompatibility\" looks like for\n// that origin, and those types don't have one yet.\nconst rootCauseShapes: Partial<Record<GraphNode['type'], RootCauseShape>> = {\n [NodeType.DatabaseNode]: databaseRootCauseShape,\n [NodeType.ServiceNode]: serviceRootCauseShape,\n [NodeType.FileNode]: fileRootCauseShape,\n}\n\nexport function getRootCause(\n graph: NeatGraph,\n errorNodeId: string,\n errorEvent?: ErrorEvent,\n incidents?: ErrorEvent[],\n): RootCauseResult | null {\n if (!graph.hasNode(errorNodeId)) return null\n const origin = graph.getNodeAttributes(errorNodeId) as GraphNode\n const shape = rootCauseShapes[origin.type]\n\n if (shape) {\n const walk = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH)\n const match = shape(graph, origin, walk)\n if (match) {\n const reason = errorEvent\n ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})`\n : match.rootCauseReason\n\n // Schema-validate before return (ADR-036, #139). A drift in the result\n // shape becomes a runtime throw at the call site rather than a silently\n // malformed payload reaching MCP / REST consumers.\n return RootCauseResultSchema.parse({\n rootCauseNode: match.rootCauseNode,\n rootCauseReason: reason,\n traversalPath: walk.path,\n edgeProvenances: walk.edges.map((e) => e.provenance),\n confidence: confidenceFromMix(walk.edges),\n fixRecommendation: match.fixRecommendation,\n })\n }\n }\n\n // A service surfacing a failure may be the entry point of a cross-service\n // 500 that actually originates downstream. Nothing calls the entry service,\n // so the incoming walk above is empty — but its own OBSERVED CALLS edge to\n // the callee carries the failure. Follow that outbound failing CALLS chain to\n // the real culprit's handler before self-attributing the caller's mislabelled\n // CLIENT span (#589). Only null-returns here when no downstream call is\n // failing, i.e. the failure is in process at the origin.\n if (origin.type === NodeType.ServiceNode) {\n const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent)\n if (crossService) return crossService\n }\n\n // No graph edge carried an incompatibility and no downstream call is failing —\n // but a service can fail in process (a 500 thrown inside its own handler)\n // without that failure ever crossing an edge, so the walk above sees a\n // healthy-looking node. The recorded incident store is the OBSERVED evidence\n // the graph can't carry: it localizes the failure to the file:line / route the\n // failing span captured. Consulting it here keeps root-cause useful for the\n // in-process case instead of reporting \"healthy\" over a pile of 500s (#584).\n return rootCauseFromIncidents(errorNodeId, incidents, errorEvent)\n}\n\n// OBSERVED-grade confidence for an incident-localized cause. The incident is a\n// real captured runtime fact (where the failure surfaced), but it names the\n// surface, not a proven upstream incompatibility — so it sits below an\n// edge-walked compat result yet well above an EXTRACTED guess.\nconst INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6\n\n// Match an incident to the queried node the same way the REST incident-history\n// read does (api.ts): an exact affectedNode hit, or a service match when the\n// node is the service the incident was recorded against. A file-grained\n// affectedNode (file:<svc>:<path>) still matches the owning service this way.\nfunction incidentMatchesNode(ev: ErrorEvent, nodeId: string): boolean {\n return ev.affectedNode === nodeId || ev.service === nodeId.replace(/^service:/, '')\n}\n\n// A failure localized to a node through the incident store: which node carries\n// the cause, the human reason, the file the failure surfaced in (when the\n// incident captured a `code.*` call site), and the derived fix.\ninterface IncidentLocalization {\n rootCauseNode: string\n rootCauseReason: string\n // The FileNode the failure surfaced in, present only when the incident\n // localized to a file grain. Callers walk node → file as a single OBSERVED\n // hop when this is set.\n fileNode?: string\n fixRecommendation?: string\n}\n\n// Pick the most recent incident affecting `nodeId` and localize the failure to\n// the file:line / route it captured. Returns null when no incident touches the\n// node. Shared by the in-process fallback and the cross-service chain (#589) so\n// both describe a culprit's handler the same way.\nfunction localizeFromIncidents(\n nodeId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): IncidentLocalization | null {\n const pool = incidents && incidents.length > 0 ? incidents : errorEvent ? [errorEvent] : []\n const relevant = pool.filter((ev) => incidentMatchesNode(ev, nodeId))\n if (relevant.length === 0) return null\n\n // Most recent incident is the representative; ISO timestamps sort lexically.\n const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0]!\n const attrs = latest.attributes ?? {}\n const filepath = typeof attrs['code.filepath'] === 'string' ? attrs['code.filepath'] : undefined\n const lineno = typeof attrs['code.lineno'] === 'number' ? attrs['code.lineno'] : undefined\n const route = typeof attrs['http.route'] === 'string' ? attrs['http.route'] : undefined\n const location = filepath ? `${filepath}${lineno !== undefined ? `:${lineno}` : ''}` : undefined\n\n // Count the incidents of *this* failure mode, not every incident on the node.\n // The reason names one message (`latest.errorMessage`); pairing it with the\n // node's total incident count reads as though that one error happened N times\n // when the node may be failing several different ways. Scope the count to the\n // records sharing this message so \"3 recorded incidents\" means three of the\n // failure the reason actually describes (issue #624).\n const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage)\n const count = sameMode.length\n const tail = count > 1 ? ` (${count} recorded incidents)` : ' (1 recorded incident)'\n const reasonParts = [`${latest.service}: ${latest.errorMessage}`]\n if (location) reasonParts.push(`surfaced at ${location}`)\n const rootCauseReason = `${reasonParts.join(' — ')}${tail}`\n\n // When the incident localized to a file (affectedNode is a file id), name\n // that file as the root cause. The \"edge\" the caller walks is the captured\n // runtime attribution; OBSERVED is honest because the file came from a real\n // `code.*` on the failing span. Otherwise the cause sits on the node itself.\n const localizesToFile = latest.affectedNode !== nodeId && latest.affectedNode.startsWith('file:')\n const fileNode = localizesToFile ? latest.affectedNode : undefined\n\n const fixRecommendation = location\n ? `Inspect ${location}${route ? ` handling ${route}` : ''}`\n : route\n ? `Inspect ${latest.service}'s handler for ${route}`\n : undefined\n\n return {\n rootCauseNode: fileNode ?? nodeId,\n rootCauseReason,\n ...(fileNode ? { fileNode } : {}),\n ...(fixRecommendation ? { fixRecommendation } : {}),\n }\n}\n\n// Build a root-cause result from the recorded incident store when the graph\n// walk found nothing. Localizes the failure to the queried node itself (or the\n// file it surfaced in). Returns null when no incident touches the node — the\n// honest \"nothing to say\" answer.\nfunction rootCauseFromIncidents(\n nodeId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): RootCauseResult | null {\n const loc = localizeFromIncidents(nodeId, incidents, errorEvent)\n if (!loc) return null\n\n const traversalPath = loc.fileNode ? [nodeId, loc.fileNode] : [nodeId]\n const edgeProvenances = loc.fileNode ? [Provenance.OBSERVED] : []\n\n return RootCauseResultSchema.parse({\n rootCauseNode: loc.rootCauseNode,\n rootCauseReason: loc.rootCauseReason,\n traversalPath,\n edgeProvenances,\n confidence: INCIDENT_ROOT_CAUSE_CONFIDENCE,\n ...(loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}),\n })\n}\n\n// A CALLS edge counts as failing when its OBSERVED signal recorded at least one\n// error. This is the signal the cross-service chain follows: the caller's call\n// to the callee returned a 5xx (#589).\nfunction isFailingCallEdge(e: GraphEdge): boolean {\n return e.type === EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0\n}\n\n// Every node id that can originate an outbound CALLS edge on a service's behalf:\n// the service itself, plus each FileNode it CONTAINS. A file-first graph anchors\n// the caller's CALLS edge on the call-site file (file-awareness.md §4), so an\n// entry service's failing call may hang off one of its files, not the bare\n// service node.\nfunction callSourcesForService(graph: NeatGraph, serviceId: string): string[] {\n const ids = [serviceId]\n for (const edgeId of graph.outboundEdges(serviceId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const tgt = graph.getNodeAttributes(e.target) as GraphNode\n if (tgt.type === NodeType.FileNode) ids.push(e.target)\n }\n return ids\n}\n\n// Did edge `e` to service `id` beat the current best failing call? Most recorded\n// errors win; ties break on PROV_RANK, then target id — deterministic.\nfunction failingCallDominates(\n e: GraphEdge,\n id: string,\n curEdge: GraphEdge,\n curId: string,\n): boolean {\n const ec = e.signal?.errorCount ?? 0\n const cc = curEdge.signal?.errorCount ?? 0\n if (ec !== cc) return ec > cc\n if (PROV_RANK[e.provenance] !== PROV_RANK[curEdge.provenance]) {\n return PROV_RANK[e.provenance] > PROV_RANK[curEdge.provenance]\n }\n return id < curId\n}\n\n// The dominant failing outbound CALLS from a service: among the service's own\n// edges and those of the files it owns, the failing CALLS edge to another\n// service with the most recorded errors. Returns the next-hop service id and the\n// edge, or null when no downstream call is failing — meaning the failure is in\n// process here, not relayed from deeper.\nfunction dominantFailingCall(\n graph: NeatGraph,\n serviceId: string,\n visited: Set<string>,\n): { nextService: string; edge: GraphEdge } | null {\n let best: { nextService: string; edge: GraphEdge } | null = null\n for (const src of callSourcesForService(graph, serviceId)) {\n for (const edgeId of graph.outboundEdges(src)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (!isFailingCallEdge(e)) continue\n if (isFrontierNode(graph, e.target)) continue\n const owner = resolveOwningService(graph, e.target)\n if (!owner || visited.has(owner.id)) continue\n if (!best || failingCallDominates(e, owner.id, best.edge, best.nextService)) {\n best = { nextService: owner.id, edge: e }\n }\n }\n }\n return best\n}\n\n// Walk the failing CALLS chain outbound from an entry service to the deepest\n// still-failing callee — the service whose own downstream calls are clean and\n// whose handler therefore threw (#589). Returns the path of service ids, the\n// failing edges along it, and the culprit, or null when nothing downstream is\n// failing.\nfunction followFailingCallChain(\n graph: NeatGraph,\n originServiceId: string,\n maxDepth: number,\n): { path: string[]; edges: GraphEdge[]; culprit: string } | null {\n const path = [originServiceId]\n const edges: GraphEdge[] = []\n const visited = new Set<string>([originServiceId])\n let current = originServiceId\n\n for (let depth = 0; depth < maxDepth; depth++) {\n const hop = dominantFailingCall(graph, current, visited)\n if (!hop) break\n path.push(hop.nextService)\n edges.push(hop.edge)\n visited.add(hop.nextService)\n current = hop.nextService\n }\n\n if (edges.length === 0) return null\n return { path, edges, culprit: current }\n}\n\n// Localize a cross-service failure (#589). An entry ServiceNode surfaces a 500\n// that originates downstream: follow the failing CALLS chain to the culprit and\n// describe its handler, never the caller's mis-attributed CLIENT span. Returns\n// null when no outbound call is failing — the failure is in process here and the\n// caller falls through to the origin's own incident store.\nfunction crossServiceRootCause(\n graph: NeatGraph,\n originId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): RootCauseResult | null {\n const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH)\n if (!chain) return null\n\n const culprit = chain.culprit\n const path = [...chain.path]\n const edgeProvenances = chain.edges.map((e) => e.provenance)\n\n // Cross-service confidence cascades over the failing CALLS edges and the\n // incident-localization hop, so it lands below an edge-walked compat result.\n const baseConfidence = confidenceFromMix(chain.edges)\n const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE))\n\n const loc = localizeFromIncidents(culprit, incidents, errorEvent)\n if (loc) {\n let rootCauseNode = culprit\n if (loc.fileNode) {\n path.push(loc.fileNode)\n edgeProvenances.push(Provenance.OBSERVED)\n rootCauseNode = loc.fileNode\n }\n return RootCauseResultSchema.parse({\n rootCauseNode,\n rootCauseReason: loc.rootCauseReason,\n traversalPath: path,\n edgeProvenances,\n confidence,\n ...(loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}),\n })\n }\n\n // No recorded incident for the culprit — still better than blaming the caller.\n // Name the culprit service and read the reason off the failing edge.\n const lastEdge = chain.edges[chain.edges.length - 1]!\n const errs = lastEdge.signal?.errorCount ?? 0\n const culpritName = culprit.replace(/^service:/, '')\n return RootCauseResultSchema.parse({\n rootCauseNode: culprit,\n rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? '' : 's'})`,\n traversalPath: path,\n edgeProvenances,\n confidence,\n fixRecommendation: `Inspect ${culpritName}'s failing handler`,\n })\n}\n\n// BFS along *inbound* edges from origin — the origin's dependents, i.e. what\n// breaks if the origin changes or fails (get-blast-radius.md, superseding\n// ADR-038's outbound direction). An edge `A ──depends-on──▶ B` means A breaks\n// when B changes, so the blast radius of B walks back along inbound edges to A\n// and everything that transitively depends on it. For an inbound edge the\n// neighbour is the edge's `source` (the dependent), so selection uses\n// bestEdgeBySource — the same machinery getRootCause walks inbound with.\n// Records each reachable dependent with the shortest distance back to origin\n// and the provenance of the edge that brought us to it. A sink (a database,\n// shared lib, leaf util) has no outbound edges but does have inbound ones, so\n// this is what makes its blast radius non-empty.\nexport function getBlastRadius(\n graph: NeatGraph,\n nodeId: string,\n maxDepth = BLAST_RADIUS_DEFAULT_DEPTH,\n): BlastRadiusResult {\n if (!graph.hasNode(nodeId)) {\n return BlastRadiusResultSchema.parse({ origin: nodeId, affectedNodes: [], totalAffected: 0 })\n }\n\n // Each frame carries its full predecessor chain so the affected-node payload\n // can surface `path` (origin → ... → nodeId) and `confidence` (cascaded over\n // every edge along that path). The BFS visits each reachable node once on\n // its shortest-distance path; later frames at greater distance are dropped.\n interface Frame {\n nodeId: string\n distance: number\n path: string[]\n pathEdges: GraphEdge[]\n }\n\n const seen = new Map<string, BlastRadiusAffectedNode>()\n const queue: Frame[] = [{ nodeId, distance: 0, path: [nodeId], pathEdges: [] }]\n const enqueued = new Set<string>([nodeId])\n\n while (queue.length > 0) {\n const frame = queue.shift()!\n if (frame.distance > 0 && frame.pathEdges.length > 0) {\n const lastEdge = frame.pathEdges[frame.pathEdges.length - 1]!\n seen.set(frame.nodeId, {\n nodeId: frame.nodeId,\n distance: frame.distance,\n edgeProvenance: lastEdge.provenance,\n path: frame.path,\n confidence: confidenceFromMix(frame.pathEdges),\n })\n }\n if (frame.distance >= maxDepth) continue\n\n const incoming = bestEdgeBySource(graph, graph.inboundEdges(frame.nodeId))\n for (const [srcId, edge] of incoming) {\n if (enqueued.has(srcId)) continue\n enqueued.add(srcId)\n queue.push({\n nodeId: srcId,\n distance: frame.distance + 1,\n path: [...frame.path, srcId],\n pathEdges: [...frame.pathEdges, edge],\n })\n }\n }\n\n const affectedNodes = [...seen.values()].sort(\n (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId),\n )\n return BlastRadiusResultSchema.parse({\n origin: nodeId,\n affectedNodes,\n totalAffected: affectedNodes.length,\n })\n}\n\n// Default + max depth for transitive get_dependencies (issue #144). Default\n// 3 keeps the output legible at the agent layer; the contract caps the\n// caller-supplied value at 10 to prevent BFS blow-up on dense graphs.\nexport const TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH = 3\nexport const TRANSITIVE_DEPENDENCIES_MAX_DEPTH = 10\n\n// Transitive get_dependencies (ADR-039 / #144). BFS outbound from origin to\n// `depth` hops, returning a flat list with distance, edgeType, and provenance\n// per dependency. Origin is never in the list. Direct-only consumers pass\n// depth=1; the MCP get_dependencies tool defaults to 3.\n//\n// Reuses bestEdgeByTarget (FRONTIER filtered, PROV_RANK-best per pair) so\n// dedup behavior matches the rest of traversal. Result is schema-validated\n// before return per ADR-036 §Result schema validation.\nexport function getTransitiveDependencies(\n graph: NeatGraph,\n nodeId: string,\n depth: number = TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,\n): TransitiveDependenciesResult {\n if (!graph.hasNode(nodeId)) {\n return TransitiveDependenciesResultSchema.parse({\n origin: nodeId,\n depth,\n dependencies: [],\n total: 0,\n })\n }\n\n interface Frame {\n nodeId: string\n distance: number\n edge: GraphEdge | null\n }\n\n const seen = new Map<string, TransitiveDependency>()\n const queue: Frame[] = [{ nodeId, distance: 0, edge: null }]\n const enqueued = new Set<string>([nodeId])\n\n while (queue.length > 0) {\n const frame = queue.shift()!\n if (frame.distance > 0 && frame.edge) {\n seen.set(frame.nodeId, {\n nodeId: frame.nodeId,\n distance: frame.distance,\n edgeType: frame.edge.type,\n provenance: frame.edge.provenance,\n })\n }\n if (frame.distance >= depth) continue\n\n const outgoing = bestEdgeByTarget(graph, graph.outboundEdges(frame.nodeId))\n for (const [tgtId, edge] of outgoing) {\n if (enqueued.has(tgtId)) continue\n enqueued.add(tgtId)\n queue.push({ nodeId: tgtId, distance: frame.distance + 1, edge })\n }\n }\n\n const dependencies = [...seen.values()].sort(\n (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId),\n )\n return TransitiveDependenciesResultSchema.parse({\n origin: nodeId,\n depth,\n dependencies,\n total: dependencies.length,\n })\n}\n\n// Observed-only dependencies (issue #578). \"What does this node actually call at\n// runtime?\" — its OBSERVED outbound edges, file-grained.\n//\n// The subtlety the previous edges-only query missed: the call-site processor\n// lands OBSERVED CALLS on the FileNode that made the call, not on the owning\n// ServiceNode (file-awareness §4). So a query that starts at a ServiceNode sees\n// only its structural `CONTAINS` edges and reports \"no runtime traffic,\" while\n// the real dependency sits one hop away on a file it owns. When the origin is a\n// ServiceNode we therefore also read the OBSERVED outbound of the FileNodes it\n// `CONTAINS` and surface those file→target edges. This is not a service rollup\n// (file-awareness §3): the edges stay file-grained with the owning file as the\n// source; the service is only the grouping the query entered through.\n//\n// `observed` and `inboundObservedCount` separate two cases the old copy\n// conflated: a pure receiver — a node runtime hits but which calls nothing\n// downstream — has zero dependencies yet is plainly seen by OTel, so the caller\n// must not ask \"is OTel running?\" at it. That question is honest only when there\n// is no OBSERVED traffic at all and EXTRACTED outbound edges exist\n// (`hasExtractedOutbound`).\nexport function getObservedDependencies(\n graph: NeatGraph,\n nodeId: string,\n): ObservedDependenciesResult {\n if (!graph.hasNode(nodeId)) {\n return ObservedDependenciesResultSchema.parse({\n origin: nodeId,\n dependencies: [],\n observed: false,\n inboundObservedCount: 0,\n hasExtractedOutbound: false,\n })\n }\n\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n\n // The origin plus, when it's a service, the files it owns — the set of nodes\n // whose OBSERVED edges belong to \"what this thing does at runtime.\"\n const scope: string[] = [nodeId]\n if (attrs.type === NodeType.ServiceNode) {\n for (const edgeId of graph.outboundEdges(nodeId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const owned = graph.getNodeAttributes(e.target) as GraphNode\n if (owned.type === NodeType.FileNode) scope.push(e.target)\n }\n }\n\n const dependencies: GraphEdge[] = []\n const seenEdge = new Set<string>()\n let hasExtractedOutbound = false\n for (const src of scope) {\n for (const edgeId of graph.outboundEdges(src)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n // CONTAINS is structural ownership, never a runtime dependency.\n if (e.type === EdgeType.CONTAINS) continue\n if (e.provenance === Provenance.OBSERVED) {\n if (!seenEdge.has(e.id)) {\n seenEdge.add(e.id)\n dependencies.push(e)\n }\n } else if (e.provenance === Provenance.EXTRACTED) {\n hasExtractedOutbound = true\n }\n }\n }\n\n // Was this node (or a file it owns) seen receiving traffic? Counting OBSERVED\n // inbound edges is the pure-receiver signal — the \"hit N times, calls nothing\"\n // shape that must read differently from \"never observed.\"\n let inboundObservedCount = 0\n for (const tgt of scope) {\n for (const edgeId of graph.inboundEdges(tgt)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONTAINS) continue\n if (e.provenance === Provenance.OBSERVED) inboundObservedCount += 1\n }\n }\n\n dependencies.sort(\n (a, b) =>\n a.target.localeCompare(b.target) ||\n a.source.localeCompare(b.source) ||\n a.id.localeCompare(b.id),\n )\n\n return ObservedDependenciesResultSchema.parse({\n origin: nodeId,\n dependencies,\n observed: dependencies.length > 0 || inboundObservedCount > 0,\n inboundObservedCount,\n hasExtractedOutbound,\n })\n}\n","import { promises as fs, existsSync, readFileSync } from 'node:fs'\nimport path from 'node:path'\nimport * as sourceMapJs from 'source-map-js'\nimport type {\n DatabaseNode,\n ErrorEvent,\n FileNode,\n FrontierNode,\n GraphEdge,\n GraphNode,\n GraphQLOperationNode,\n GrpcMethodNode,\n InfraNode,\n Policy,\n ServiceNode,\n StaleEvent,\n WebSocketChannelNode,\n} from '@neat.is/types'\nimport type { PersistedGraph } from './persist.js'\nimport type { EvaluationContext as PolicyEvaluationContext } from './policy.js'\nimport { canPromoteFrontier } from './policy.js'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForObservedSignal,\n databaseId,\n localDatabaseId,\n extractedEdgeId,\n fileId,\n frontierId,\n graphqlOperationId,\n grpcMethodId,\n inferredEdgeId,\n infraId,\n observedEdgeId,\n serviceId,\n websocketChannelId,\n type EdgeTypeValue,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport type { ParsedSpan } from './otel.js'\nimport { emitNeatEvent } from './events.js'\n\n// Maps OTel spans to graph signal:\n// * Cross-service span → upsert CALLS edge.\n// * Database span (db.system attr present) → upsert CONNECTS_TO edge to a\n// DatabaseNode resolved by host, or a service-scoped local DatabaseNode when\n// the span carries no peer host (an in-process / embedded DB, ADR-118).\n// * Span with status.code === 2 → ErrorEvent appended to errors.ndjson.\n//\n// Contract anchors (see /docs/contracts.md):\n// * Rule 1 — Provenance: every edge here carries Provenance.X from @neat.is/types.\n// * Rule 2 — Coexistence: OBSERVED edges live alongside EXTRACTED ones with a\n// distinct id pattern (`${type}:OBSERVED:src->tgt`). Never write OBSERVED\n// under the EXTRACTED id; that erases the gap NEAT exists to surface.\n// * Rule 4 — Per-edge-type staleness (ADR-024): STALE_THRESHOLDS_BY_EDGE_TYPE\n// governs decay; never hardcode a flat 24h threshold.\n// * Rule 8 — No demo names: derive driver/engine identifiers from node\n// properties, not literals.\n\nexport interface IngestContext {\n graph: NeatGraph\n errorsPath: string\n // Absolute scan root the daemon is watching for this project. When set, a\n // runtime `code.filepath` is made service-root-relative against it before the\n // FileNode is keyed (file-awareness.md §4) — the service's absolute root is\n // `scanPath/<repoPath>`, which recovers `dist/foo.js` even for a single-\n // package service whose `repoPath` is empty (issue #430). Omitted by ad-hoc\n // callers and most tests, which rely on the repoPath-segment anchor instead.\n scanPath?: string\n // Project name for event-bus routing (ADR-051). Defaults to DEFAULT_PROJECT\n // when omitted — keeps single-project tests / scripts wire-compatible.\n project?: string\n now?: () => number\n // Set to false when the receiver already wrote the ErrorEvent synchronously\n // (production daemons via watch.ts wire this). When true or omitted, handleSpan\n // appends the ErrorEvent itself — the path used by ad-hoc scripts and tests\n // that don't go through buildOtelReceiver. ADR-033 §Error events.\n writeErrorEventInline?: boolean\n // Post-mutation policy trigger (ADR-043). Fires after handleSpan finishes\n // and the queue is drained. Daemons wire this to evaluateAllPolicies +\n // PolicyViolationsLog.append. Ad-hoc callers leave it undefined; their tests\n // don't need policy side effects.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n // 4xx-burst coalescing state, keyed by `${source}->${peer}` (issue #481).\n // Lazily created the first time handleSpan sees a 4xx CLIENT/PRODUCER span.\n // Carried on the context so each project/daemon keeps its own bursts and a\n // long-lived handler accumulates across spans; ad-hoc callers reuse one ctx\n // across a batch and get the same coalescing.\n burstState?: Map<string, BurstState>\n}\n\nconst HOUR_MS = 60 * 60 * 1000\nconst DAY_MS = 24 * HOUR_MS\n\n// Per-edge-type stale thresholds. HTTP CALLS at 24h is meaningless because\n// healthy traffic recurs in seconds; infra DEPENDS_ON is the opposite — a\n// docker-compose service can sit idle overnight without anything being wrong.\n// Override via NEAT_STALE_THRESHOLDS (JSON, ms-per-edge-type).\nconst DEFAULT_STALE_THRESHOLDS: Record<string, number> = {\n CALLS: HOUR_MS,\n CONNECTS_TO: 4 * HOUR_MS,\n PUBLISHES_TO: 4 * HOUR_MS,\n CONSUMES_FROM: 4 * HOUR_MS,\n DEPENDS_ON: DAY_MS,\n CONFIGURED_BY: DAY_MS,\n RUNS_ON: DAY_MS,\n}\n// Fallback for any edge type not in the map (forward compat — adding a new\n// EdgeType shouldn't break staleness sweeps).\nconst FALLBACK_STALE_THRESHOLD_MS = DAY_MS\n\nfunction loadStaleThresholdsFromEnv(): Record<string, number> {\n const raw = process.env.NEAT_STALE_THRESHOLDS\n if (!raw) return DEFAULT_STALE_THRESHOLDS\n try {\n const overrides = JSON.parse(raw) as Record<string, unknown>\n const merged = { ...DEFAULT_STALE_THRESHOLDS }\n for (const [k, v] of Object.entries(overrides)) {\n if (typeof v === 'number' && Number.isFinite(v) && v >= 0) merged[k] = v\n }\n return merged\n } catch (err) {\n console.warn(\n `[neat] NEAT_STALE_THRESHOLDS could not be parsed (${(err as Error).message}); using defaults`,\n )\n return DEFAULT_STALE_THRESHOLDS\n }\n}\n\nexport function thresholdForEdgeType(\n edgeType: string,\n overrides?: Record<string, number>,\n): number {\n const map = overrides ?? loadStaleThresholdsFromEnv()\n return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS\n}\n\n// Failing-response incident tuning. A span that completes 5xx, carries an\n// ERROR status, or an exception event records an incident on its own — those\n// are unambiguous failures. A 4xx CLIENT/PRODUCER span doesn't: a single 404 is\n// often correct app behavior (auth probe, conditional fetch). 4xx becomes a\n// signal only when it repeats — N consecutive 4xx against the same (source,\n// peer) pair inside a window record ONE coalesced incident carrying the count\n// and the dominant status code, rather than N separate lines that would drown\n// the history. Mirrors the NEAT_STALE_THRESHOLDS override shape.\n// threshold — how many consecutive 4xx against one peer trip the burst.\n// windowMs — the gap that ends a burst; a 4xx more than this after the\n// previous one starts a fresh burst rather than extending it.\nconst DEFAULT_INCIDENT_THRESHOLDS = {\n threshold: 5,\n windowMs: 60_000,\n}\n\nfunction loadIncidentThresholdsFromEnv(): { threshold: number; windowMs: number } {\n const raw = process.env.NEAT_INCIDENT_THRESHOLDS\n if (!raw) return DEFAULT_INCIDENT_THRESHOLDS\n try {\n const overrides = JSON.parse(raw) as Record<string, unknown>\n const merged = { ...DEFAULT_INCIDENT_THRESHOLDS }\n if (\n typeof overrides.threshold === 'number' &&\n Number.isFinite(overrides.threshold) &&\n overrides.threshold >= 1\n ) {\n merged.threshold = Math.floor(overrides.threshold)\n }\n if (\n typeof overrides.windowMs === 'number' &&\n Number.isFinite(overrides.windowMs) &&\n overrides.windowMs >= 0\n ) {\n merged.windowMs = overrides.windowMs\n }\n return merged\n } catch (err) {\n console.warn(\n `[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${(err as Error).message}); using defaults`,\n )\n return DEFAULT_INCIDENT_THRESHOLDS\n }\n}\n\n// An attribute bag — either a live span's `attributes` or the passthrough set a\n// recorded ErrorEvent carries. The message helpers read from both, so the same\n// \"what failed here\" logic that names an incident at record time can re-derive\n// it at read time (dedupeIncidents).\ntype AttrBag = Record<string, unknown>\n\n// Read the HTTP response status off an attribute bag. OTel semconv renamed this\n// attribute — modern SDKs write `http.response.status_code`, older ones\n// `http.status_code`. Returns undefined when neither is present or parseable, so\n// a span with no response status is never misclassified as a failure.\nfunction httpResponseStatusFromAttrs(attrs: AttrBag): number | undefined {\n for (const key of ['http.response.status_code', 'http.status_code']) {\n const v = attrs[key]\n if (typeof v === 'number' && Number.isFinite(v)) return v\n if (typeof v === 'string') {\n const n = Number(v)\n if (Number.isFinite(n)) return n\n }\n }\n return undefined\n}\n\nfunction httpResponseStatus(span: ParsedSpan): number | undefined {\n return httpResponseStatusFromAttrs(span.attributes)\n}\n\n// A human incident line built from the HTTP context a server span carries even\n// when no exception event was recorded — an Express error handler that answers\n// 500 cleanly leaves `span.exception` empty but still carries the route and\n// status. \"500 on GET /users/:id\" reads better than the literal 'unknown\n// error'. Returns undefined when the bag has no usable HTTP context, so a\n// non-HTTP failure falls through to nonHttpFailureMessage / 'unknown error'.\n// Method/route follow the OTel semconv rename (modern `http.request.method` /\n// legacy `http.method`; `http.route` matched template, `http.target` / `url.path`\n// concrete-path fallback).\nfunction httpFailureMessageFromAttrs(attrs: AttrBag): string | undefined {\n const status = httpResponseStatusFromAttrs(attrs)\n const route = pickAttrFrom(attrs, 'http.route', 'http.target', 'url.path')\n const method = pickAttrFrom(attrs, 'http.request.method', 'http.method')\n const where = route ? `${method ? `${method} ` : ''}${route}` : undefined\n if (status !== undefined && where) return `${status} on ${where}`\n if (status !== undefined) return `HTTP ${status}`\n if (where) return `error on ${where}`\n return undefined\n}\n\n// Canonical gRPC status code → name (grpc/status.proto). Fixed protocol\n// constants shared by every gRPC implementation — not driver/engine data, so\n// they don't belong in compat.json (Rule 8 governs the latter, not a wire enum).\nconst GRPC_STATUS_NAMES: Record<number, string> = {\n 1: 'CANCELLED',\n 2: 'UNKNOWN',\n 3: 'INVALID_ARGUMENT',\n 4: 'DEADLINE_EXCEEDED',\n 5: 'NOT_FOUND',\n 6: 'ALREADY_EXISTS',\n 7: 'PERMISSION_DENIED',\n 8: 'RESOURCE_EXHAUSTED',\n 9: 'FAILED_PRECONDITION',\n 10: 'ABORTED',\n 11: 'OUT_OF_RANGE',\n 12: 'UNIMPLEMENTED',\n 13: 'INTERNAL',\n 14: 'UNAVAILABLE',\n 15: 'DATA_LOSS',\n 16: 'UNAUTHENTICATED',\n}\n\nfunction grpcStatusCodeFromAttrs(attrs: AttrBag): number | undefined {\n const v = attrs['rpc.grpc.status_code']\n if (typeof v === 'number' && Number.isFinite(v)) return v\n if (typeof v === 'string') {\n const n = Number(v)\n if (Number.isFinite(n)) return n\n }\n return undefined\n}\n\n// A non-HTTP failure still carries its cause in span attributes — a non-OK gRPC\n// status, or a transport-level connection error (ECONNREFUSED reaching a peer).\n// Reading them keeps the incident from degrading to the literal 'unknown error'\n// when the span has no exception event and no HTTP response code. Returns\n// undefined for a span carrying neither, so the 'unknown error' floor still\n// applies to a genuinely opaque failure (issue #624).\nfunction nonHttpFailureMessageFromAttrs(attrs: AttrBag): string | undefined {\n const grpc = grpcStatusCodeFromAttrs(attrs)\n if (grpc !== undefined && grpc !== 0) {\n const name = GRPC_STATUS_NAMES[grpc] ?? `status ${grpc}`\n const detail = pickAttrFrom(attrs, 'rpc.grpc.status_message')\n return detail ? `gRPC ${name}: ${detail}` : `gRPC ${name}`\n }\n // Transport/connection failure — OTel's `error.type` carries the errno\n // (ECONNREFUSED, ETIMEDOUT, …) or the exception class for a call that never\n // got a response. Skip the HTTP status-class forms (\"500\", \"_OTHER\") that\n // http semconv also writes there; the HTTP path above owns those.\n const errType = pickAttrFrom(attrs, 'error.type')\n if (errType && errType !== '_OTHER' && !/^\\d+$/.test(errType)) {\n const peer = pickAttrFrom(attrs, 'server.address', 'net.peer.name', 'net.host.name')\n return peer ? `${errType} connecting to ${peer}` : errType\n }\n return undefined\n}\n\n// The incident's human message: the recorded exception first, then the HTTP\n// context a server span still carries, then a non-HTTP (gRPC / connection)\n// failure read from attributes, and only then the 'unknown error' floor. Shared\n// by every incident write path so the fallback chain can't drift between the\n// receiver's synchronous write and handleSpan's inline write.\nfunction incidentMessage(span: ParsedSpan): string {\n return (\n span.exception?.message ??\n httpFailureMessageFromAttrs(span.attributes) ??\n nonHttpFailureMessageFromAttrs(span.attributes) ??\n 'unknown error'\n )\n}\n\n// In-flight 4xx burst against one (source, peer) pair. Lives on IngestContext so\n// it survives across spans without leaking into module state shared by every\n// project. firstTs/lastTs are the span timestamps (ADR-033 — span time, not\n// wall clock); codes counts each 4xx by status so the dominant one can be named\n// when the burst flushes.\ninterface BurstState {\n count: number\n firstTs: string\n lastTs: string\n lastMs: number\n codes: Map<number, number>\n}\n\nfunction nowIso(ctx: IngestContext): string {\n return new Date(ctx.now ? ctx.now() : Date.now()).toISOString()\n}\n\n// One-time-per-session-per-project warning for spans whose resource omits\n// `service.name`. The OTel spec requires SDKs to set it; customised exporters\n// occasionally don't. Routing the span to `service:unidentified` keeps\n// diagnostic visibility intact (silent drop hides a real SDK misconfiguration);\n// the warning gives an operator one line of stderr per project to act on.\n// See docs/contracts/otlp-routing.md §Fallback when `resource.service.name`\n// is missing.\nconst unidentifiedWarnedProjects = new Set<string>()\nfunction warnUnidentifiedSpan(project: string): void {\n if (unidentifiedWarnedProjects.has(project)) return\n unidentifiedWarnedProjects.add(project)\n console.warn(\n `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`,\n )\n}\n\n// Test seam — production code never calls this. Tests that exercise the\n// once-per-session contract reset between cases so each assertion sees a\n// fresh warned-set.\nexport function resetUnidentifiedSpanWarnings(): void {\n unidentifiedWarnedProjects.clear()\n}\n\n// One-time-per-session-per-service audit for a compiled `dist/...js` call site\n// that carried no adjacent source map (file-awareness.md §4 + §6). Without a\n// map, ingest can't reconcile the observed dist file to the static `src/...ts`\n// the extractor parsed — the dist path is the honest answer, never a fabricated\n// src path. The leak this surfaces (issue #430) was hiding behind an absolute\n// path prefix; once the path is service-root-relative the mismatch is legible,\n// and this line tells the operator how to close it.\nconst noSourceMapWarnedServices = new Set<string>()\nfunction warnNoSourceMaps(serviceName: string): void {\n if (noSourceMapWarnedServices.has(serviceName)) return\n noSourceMapWarnedServices.add(serviceName)\n console.warn(\n `[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`,\n )\n}\n\n// Test seam — mirrors resetUnidentifiedSpanWarnings for the once-per-service\n// audit above.\nexport function resetNoSourceMapWarnings(): void {\n noSourceMapWarnedServices.clear()\n}\n\nfunction pickAttrFrom(attrs: AttrBag, ...keys: string[]): string | undefined {\n for (const k of keys) {\n const v = attrs[k]\n if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n\nfunction pickAttr(span: ParsedSpan, ...keys: string[]): string | undefined {\n return pickAttrFrom(span.attributes, ...keys)\n}\n\nfunction hostFromUrl(u: string | undefined): string | undefined {\n if (!u) return undefined\n try {\n return new URL(u).hostname\n } catch {\n return undefined\n }\n}\n\n// OTel HTTP/db semconv has gone through several names for \"the host on the\n// other end of this call.\" Try the modern ones first, fall back to the legacy\n// ones, then last resort parse out of a full URL.\nfunction pickAddress(span: ParsedSpan): string | undefined {\n return (\n pickAttr(span, 'server.address', 'net.peer.name', 'net.host.name') ??\n hostFromUrl(pickAttr(span, 'url.full', 'http.url'))\n )\n}\n\n// A loopback peer address is this host talking to itself, never a distinct\n// upstream service. Cross-service correlation on the callee's SERVER span (the\n// parent-span fallback, ADR-033) recovers the real peer, so a loopback address\n// on a CLIENT span must not mint a standalone frontier:localhost /\n// frontier:127.0.0.1 that duplicates that resolved edge (issues #590, #577).\n// Scoped to the cross-service CALLS path — a loopback database is a real local\n// dependency and keeps its CONNECTS_TO edge.\nfunction isLoopbackHost(host: string): boolean {\n const h = host.toLowerCase()\n return (\n h === 'localhost' ||\n h === 'ip6-localhost' ||\n h === '::1' ||\n h === '[::1]' ||\n /^127(?:\\.\\d{1,3}){3}$/.test(h)\n )\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Call-site capture (file-awareness.md §4)\n//\n// The injected SpanProcessor sets `code.filepath` / `code.lineno` /\n// `code.function` on CLIENT/PRODUCER spans — the exact OTel attribute names,\n// written by the emit template (installers/templates.ts) and read here. The\n// two sites are cross-referenced so the names can't drift. When present, an\n// OBSERVED relationship originates from the file rather than the service.\n// SERVER spans and the callee side carry no call site and stay service-level;\n// evidence is never fabricated (§6).\nconst CODE_FILEPATH_ATTR = 'code.filepath'\nconst CODE_LINENO_ATTR = 'code.lineno'\nconst CODE_FUNCTION_ATTR = 'code.function'\n\nfunction toPosix(p: string): string {\n return p.split('\\\\').join('/')\n}\n\nfunction languageForExt(relPath: string): string | undefined {\n const dot = relPath.lastIndexOf('.')\n if (dot === -1) return undefined\n switch (relPath.slice(dot).toLowerCase()) {\n case '.py':\n return 'python'\n case '.ts':\n case '.tsx':\n return 'typescript'\n case '.js':\n case '.jsx':\n case '.mjs':\n case '.cjs':\n return 'javascript'\n default:\n return undefined\n }\n}\n\n// Join the runtime `code.filepath` against the service root so the OBSERVED\n// relPath lines up with the EXTRACTED service-relative path (file-awareness.md\n// §4 + §7). ServiceNode.repoPath is the scanPath-relative package dir; its\n// segments appear inside the absolute runtime path, so anchoring on it recovers\n// the package-relative tail. With no usable anchor, the real runtime path is\n// returned in a relative-looking form — honest, even if it doesn't align with a\n// static src path. Never fabricated.\nfunction relPathForRuntimeFile(\n filepath: string,\n serviceNode?: ServiceNode,\n scanPath?: string,\n): string | null {\n let p = toPosix(filepath).replace(/^file:\\/\\//, '')\n // When ingest knows the absolute scan root, the service's absolute root is\n // `scanPath/<repoPath>`. Stripping it directly recovers the service-relative\n // tail (`dist/foo.js`) even for a single-package service whose `repoPath` is\n // empty — the segment anchor below has nothing to grab in that case, so the\n // absolute path used to leak into the FileNode key (issue #430).\n if (scanPath && scanPath.length > 0) {\n const absRoot = toPosix(path.resolve(scanPath, serviceNode?.repoPath ?? ''))\n const anchor = absRoot.endsWith('/') ? absRoot : `${absRoot}/`\n if (p.startsWith(anchor)) return p.slice(anchor.length)\n }\n const root = serviceNode?.repoPath\n if (root && root !== '.' && root.length > 0) {\n const rootPosix = toPosix(root)\n const anchor = `/${rootPosix}/`\n const idx = p.lastIndexOf(anchor)\n if (idx !== -1) return p.slice(idx + anchor.length)\n const base = rootPosix.split('/').filter(Boolean).pop()\n if (base) {\n const baseAnchor = `/${base}/`\n const bidx = p.lastIndexOf(baseAnchor)\n if (bidx !== -1) return p.slice(bidx + baseAnchor.length)\n }\n }\n p = p.replace(/^[A-Za-z]:/, '').replace(/^\\/+/, '')\n return p.length > 0 ? p : null\n}\n\n// Exported so the connectors plane (packages/core/src/connectors/index.ts,\n// docs/contracts/connectors.md) can build one from a signal's own callSite —\n// a connector's file:line comes from the provider's telemetry rather than an\n// OTel span's code.* attributes, but reconciles onto the same EXTRACTED\n// FileNode through the same primitives below.\nexport interface CallSite {\n relPath: string\n line?: number\n fn?: string\n // The service-relative dist path the call site was captured on, when ingest\n // resolved it through a source map to a different (source) `relPath`\n // (file-awareness.md §4). Surfaces as FileNode.originalPath. Absent when the\n // captured frame was already source-grained.\n originalRelPath?: string\n}\n\n// dist→src source-map resolution (file-awareness.md §4). A runtime call site in\n// a compiled `dist/...js` is resolved through a disk-adjacent `.map` to the\n// original `src/...ts`, so an OBSERVED edge lands on the source file an agent\n// can open. Same-host only — when the daemon's filesystem doesn't carry the map\n// (a service that ran on a different machine) the dist frame is kept, honestly,\n// never fabricated (§6). Each dist file is read from disk once: a present map\n// caches its consumer, an absent one caches `null`. Synchronous reads keep\n// callSiteFromSpan synchronous; the cost is amortised across a file's spans.\nconst sourceMapCache = new Map<\n string,\n { consumer: sourceMapJs.SourceMapConsumer; dir: string } | null\n>()\n\ninterface ResolvedSrc {\n filepath: string\n line?: number\n}\n\nfunction resolveDistToSrc(absFilepath: string, line?: number): ResolvedSrc | null {\n if (!absFilepath.endsWith('.js')) return null\n let entry = sourceMapCache.get(absFilepath)\n if (entry === undefined) {\n entry = null\n const mapPath = `${absFilepath}.map`\n try {\n if (existsSync(mapPath)) {\n const raw = JSON.parse(readFileSync(mapPath, 'utf8')) as unknown\n const consumer = new sourceMapJs.SourceMapConsumer(raw as never)\n entry = { consumer, dir: path.dirname(mapPath) }\n }\n } catch {\n entry = null\n }\n sourceMapCache.set(absFilepath, entry)\n }\n if (!entry) return null\n try {\n const pos = entry.consumer.originalPositionFor({\n line: line !== undefined && Number.isFinite(line) ? line : 1,\n column: 0,\n })\n if (!pos || !pos.source) return null\n const root = entry.consumer.sourceRoot ?? ''\n const resolved = path.resolve(entry.dir, root, pos.source)\n return { filepath: resolved, ...(pos.line ? { line: pos.line } : {}) }\n } catch {\n return null\n }\n}\n\n// Read the call-site attributes off a span. Returns null when the span carries\n// no `code.filepath` (SERVER spans, un-instrumented peers, callee side) so the\n// caller falls back to a service-level edge.\nfunction callSiteFromSpan(\n span: ParsedSpan,\n serviceNode?: ServiceNode,\n scanPath?: string,\n): CallSite | null {\n const filepath = span.attributes[CODE_FILEPATH_ATTR]\n if (typeof filepath !== 'string' || filepath.length === 0) return null\n const linenoRaw = span.attributes[CODE_LINENO_ATTR]\n let line =\n typeof linenoRaw === 'number' && Number.isFinite(linenoRaw) ? linenoRaw : undefined\n // Resolve a compiled dist frame to its source before computing the service-\n // relative path, so the FileNode lands on the original `src/...ts`.\n const abs = toPosix(filepath).replace(/^file:\\/\\//, '')\n const resolved = resolveDistToSrc(abs, line)\n let effectivePath = filepath\n let originalRelPath: string | undefined\n if (resolved) {\n originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? undefined\n effectivePath = resolved.filepath\n if (resolved.line !== undefined) line = resolved.line\n }\n const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath)\n if (!relPath) return null\n // A compiled `dist/...js` call site that didn't resolve through a map keeps\n // the (honest) dist path. Surface the absence once per service so the\n // operator can enable source maps and recover src-level reconciliation\n // (file-awareness.md §4 + §6, issue #430).\n if (!resolved && abs.endsWith('.js') && relPath.startsWith('dist/') && serviceNode?.name) {\n warnNoSourceMaps(serviceNode.name)\n }\n const fnRaw = span.attributes[CODE_FUNCTION_ATTR]\n const fn = typeof fnRaw === 'string' && fnRaw.length > 0 ? fnRaw : undefined\n return {\n relPath,\n ...(line !== undefined ? { line } : {}),\n ...(fn ? { fn } : {}),\n ...(originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}),\n }\n}\n\n// Reconcile a runtime-derived relPath onto the service-relative path the\n// extractor already minted, so OBSERVED and EXTRACTED FileNodes for the same\n// source file fuse into ONE node instead of two disjoint subgraphs\n// (file-awareness.md §4 — ingest joins the runtime path against the service\n// root to land the edge on a FileNode).\n//\n// relPathForRuntimeFile anchors the absolute `code.filepath` against scanPath /\n// repoPath. When that anchor can't be found — no scanPath wired, or the span\n// was emitted from a service whose absolute root differs from the daemon's\n// checkout (a container image rooted at `/app`, a relocated clone) — the\n// leftover relPath still carries the unanchored leading segments\n// (`app/src/foo.ts`, `Users/me/repo/src/foo.ts`) and forks a parallel FileNode\n// keyed off the absolute path. That splits the graph: the OBSERVED layer never\n// lands on the EXTRACTED `src/foo.ts` node, and divergence/traversal see two\n// half-graphs for one file.\n//\n// The extractor's FileNode paths are ground truth for which service-relative\n// paths exist. Recover the right one by matching the longest EXTRACTED (non-\n// OTel) FileNode path that is a trailing segment-suffix of the runtime relPath.\n// A match means the runtime path is the same file the extractor parsed, just\n// carrying extra leading directories the anchor couldn't strip — reuse the\n// extractor's path so both layers key the same node. No match means the file is\n// genuinely OTel-only; the honest runtime path stands (never fabricated, §6).\nexport function reconcileObservedRelPath(\n graph: NeatGraph,\n serviceName: string,\n relPath: string,\n): string {\n // Already lands on a known node (the anchor resolved cleanly, or a prior span\n // created this node) — fused, nothing to recover.\n if (graph.hasNode(fileId(serviceName, relPath))) return relPath\n let best: string | null = null\n graph.forEachNode((_id, attrs) => {\n const a = attrs as FileNode & { type?: string }\n if (a.type !== NodeType.FileNode || a.service !== serviceName) return\n // Only fuse onto a statically-known file. An existing OTel-only node would\n // already have matched the hasNode short-circuit above.\n if (a.discoveredVia === 'otel') return\n const p = a.path\n if (!p) return\n if ((relPath === p || relPath.endsWith(`/${p}`)) && (!best || p.length > best.length)) {\n best = p\n }\n })\n return best ?? relPath\n}\n\n// Ensure the FileNode for an observed call site and the owning service's\n// OBSERVED `CONTAINS` edge both exist, returning the FileNode id so the caller\n// can originate the relationship from it (file-awareness.md §1–2 + §4). The\n// CONTAINS edge carries no `lastObserved` — structural ownership doesn't go\n// STALE when traffic quiets (markStaleEdges skips edges without lastObserved),\n// and divergence detection skips CONTAINS so an OTel-only file node doesn't\n// surface as a missing-extracted finding.\nexport function ensureObservedFileNode(\n graph: NeatGraph,\n serviceName: string,\n serviceNodeId: string,\n callSite: CallSite,\n): string {\n const relPath = reconcileObservedRelPath(graph, serviceName, callSite.relPath)\n const fileNodeId = fileId(serviceName, relPath)\n if (!graph.hasNode(fileNodeId)) {\n const language = languageForExt(relPath)\n const node: FileNode = {\n id: fileNodeId,\n type: NodeType.FileNode,\n service: serviceName,\n path: relPath,\n ...(language ? { language } : {}),\n ...(callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {}),\n discoveredVia: 'otel',\n }\n graph.addNode(fileNodeId, node)\n }\n const containsId = makeObservedEdgeId(EdgeType.CONTAINS, serviceNodeId, fileNodeId)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: serviceNodeId,\n target: fileNodeId,\n type: EdgeType.CONTAINS,\n provenance: Provenance.OBSERVED,\n }\n graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge)\n }\n return fileNodeId\n}\n\n// Edge id helpers live in @neat.is/types/identity.ts (ADR-029). The local\n// signatures below preserve the (type, source, target) argument order ingest.ts\n// has used historically while delegating to the canonical wire-format helpers.\nfunction makeObservedEdgeId(type: EdgeTypeValue, source: string, target: string): string {\n return observedEdgeId(source, target, type)\n}\n\nfunction makeInferredEdgeId(type: EdgeTypeValue, source: string, target: string): string {\n return inferredEdgeId(source, target, type)\n}\n\nconst INFERRED_CONFIDENCE = 0.6\nconst STITCH_MAX_DEPTH = 2\n\n// The trace stitcher only reasons about runtime *dependency* edges — the ones an\n// error actually propagates along (a service calling a service, connecting to a\n// datastore, a declared runtime dependency). Structural edges (CONTAINS a file,\n// IMPORTS a module, CONFIGURED_BY a ConfigNode, RUNS_ON a host) are static facts\n// learned by extraction; a 500 says nothing new about them. Minting an INFERRED\n// twin of a structural EXTRACTED edge would corrupt the trust signal — the twin\n// (conf 0.6) outranks the ground-truth EXTRACTED edge (0.85) under PROV_RANK, so\n// consumer queries would surface the inference in place of the hard fact\n// (docs/contracts/trace-stitcher.md — dependency-edge-type allowlist).\nconst STITCH_EDGE_TYPES = new Set<EdgeTypeValue>([\n EdgeType.CALLS,\n EdgeType.CONNECTS_TO,\n EdgeType.DEPENDS_ON,\n])\n\n// OTLP-wire SpanKind values. The receiver decodes the raw wire integer onto\n// `ParsedSpan.kind` (otel.ts), and the wire enum is offset by one from the\n// `@opentelemetry/api` SpanKind the SDK uses in-process — UNSPECIFIED 0,\n// INTERNAL 1, SERVER 2, CLIENT 3, PRODUCER 4, CONSUMER 5. So we must NOT import\n// `@opentelemetry/api` here: its CLIENT is 2 (= wire SERVER) and PRODUCER is 3\n// (= wire CLIENT), which would gate the wrong kinds. Cross-referenced with the\n// wire fixtures in otel.test.ts (kind 2 = SERVER, kind 3 = CLIENT) and the\n// CLIENT call-site spans in ingest.test.ts (kind 3).\nconst WIRE_SPAN_KIND_CLIENT = 3\nconst WIRE_SPAN_KIND_PRODUCER = 4\nconst WIRE_SPAN_KIND_CONSUMER = 5\n\n// The caller-side gate for the CALLS / CONNECTS_TO paths. A CLIENT or PRODUCER\n// span is the caller/producer side of a service-to-service call or a datastore\n// read; INTERNAL / SERVER spans are not — a SERVER span is the callee, and its\n// edge is minted from its parent CLIENT via the parent-span fallback. Without\n// this gate every INTERNAL span that happens to carry a peer address — e.g. a\n// `tcp.connect` / `tls.connect` to an AWS endpoint — mints a spurious\n// service-level edge (issue #429), because no §4 capture layer stamps `code.*`\n// on INTERNAL spans.\n//\n// CONSUMER spans are handled by the messaging branch in handleSpan, not here:\n// a queue consumer isn't calling a service, it's reading a topic, so it mints a\n// CONSUMES_FROM edge to the destination node (spanMintsMessagingEdge below) —\n// the observed mirror of the static consumer→topic edge, the queue-side pair of\n// the PRODUCER→topic PUBLISHES_TO edge.\n//\n// A span that reports no kind (undefined) or UNSPECIFIED (0) carries no\n// caller/callee signal, so it falls back to the historical unconditional\n// behavior — hand-built and legacy producers keep minting. The leak this gates\n// is always an explicitly-kinded INTERNAL span.\nfunction spanMintsObservedEdge(kind: number | undefined): boolean {\n if (kind === undefined || kind === 0) return true\n return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER\n}\n\n// The messaging counterpart to the caller-side gate. A PRODUCER span publishes\n// to a destination; a CONSUMER span reads from one. Both sides mint an OBSERVED\n// edge to the topic/queue node — the PRODUCER a PUBLISHES_TO, the CONSUMER a\n// CONSUMES_FROM — so declared and observed queue topology fuse. Only spans that\n// actually carry a messaging destination reach this (handleSpan checks the\n// semconv attrs first), so a stray CONSUMER span with no destination stays inert.\nfunction spanMintsMessagingEdge(kind: number | undefined): boolean {\n return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER\n}\n\n// The GraphQL execution span is emitted by the service that RESOLVES the\n// operation — a SERVER or INTERNAL span (the instrumentation's `graphql.execute`),\n// never the CLIENT side that only ever sees an opaque `POST /graphql`. Gating out\n// the caller/producer/consumer kinds keeps a client-side operation span from\n// attributing the operation to the caller (client-side operation attribution is\n// deferred, ADR-122); an unkinded / UNSET span still mints, mirroring the\n// caller-side gate's legacy-producer fallback.\nfunction spanServesGraphqlOperation(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A GraphQL operation node keyed on (service, operationType, operationName) via\n// graphqlOperationId (ADR-122). Minted observed-first from the execution span so\n// operation-level topology is legible even before any static GraphQL extractor\n// exists; a later static extractor fuses onto the same id. `operationType` is\n// stored lower-cased to match the id's normalisation, so a `query` observed and a\n// `Query` static resolver land on one node. Idempotent — a high-volume operation\n// upserts, never grows the node set.\nfunction ensureGraphqlOperationNode(\n graph: NeatGraph,\n serviceName: string,\n operationType: string,\n operationName: string,\n): string {\n const id = graphqlOperationId(serviceName, operationType, operationName)\n if (graph.hasNode(id)) return id\n const node: GraphQLOperationNode = {\n id,\n type: NodeType.GraphQLOperationNode,\n name: operationName,\n service: serviceName,\n operationType: operationType.toLowerCase(),\n operationName,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// The gRPC execution span carrying `rpc.service` / `rpc.method` is emitted on\n// both sides of a call — the SERVER that resolves the method and the CLIENT that\n// invokes it. Only the serving side owns the method (ADR-123): gating out the\n// caller/producer/consumer kinds keeps a CLIENT gRPC span from attributing\n// ownership to the caller (that client span still falls through to the\n// cross-service resolver below, so the caller→callee edge is unaffected).\n// Client→method attribution is deferred. An unkinded / UNSET span still mints,\n// mirroring the graphql and caller-side gates' legacy fallbacks.\nfunction spanServesGrpcMethod(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A gRPC method node keyed on (rpcService, rpcMethod) via grpcMethodId (ADR-123).\n// `rpcService` is the fully-qualified `rpc.service` the wire carries verbatim\n// (`orders.OrderService`), so an OBSERVED span and a static `.proto` definition\n// fuse onto one node rather than twinning. Idempotent — a high-volume method\n// upserts, never grows the node set.\nfunction ensureGrpcMethodNode(\n graph: NeatGraph,\n rpcService: string,\n rpcMethod: string,\n): string {\n const id = grpcMethodId(rpcService, rpcMethod)\n if (graph.hasNode(id)) return id\n const node: GrpcMethodNode = {\n id,\n type: NodeType.GrpcMethodNode,\n name: `${rpcService}/${rpcMethod}`,\n rpcService,\n rpcMethod,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// The HTTP upgrade span that opens a WebSocket is emitted by the service that\n// SERVES the channel — a SERVER span (the framework's inbound `GET`), or an\n// unkinded / INTERNAL span from a hand-built handshake. The CLIENT that dials the\n// socket carries the same upgrade header, but the channel belongs to the server\n// (ADR-125), so gating out the caller/producer/consumer kinds keeps a client-side\n// upgrade span from attributing the channel to the caller. An unkinded / UNSET\n// span still mints, mirroring the graphql and gRPC serving-side gates.\nfunction spanServesWebsocketChannel(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A WebSocket channel node keyed on (service, channel) via websocketChannelId\n// (ADR-125). Minted OBSERVED-only from the upgrade span — a WebSocket channel is\n// known from observation, never from static extraction, so there is no declared\n// twin to fuse with and no static producer to fill in `path` / `line` (those stay\n// absent, never fabricated — file-awareness.md §6). Idempotent — every reconnect\n// on the same channel upserts, never grows the node set.\nfunction ensureWebsocketChannelNode(\n graph: NeatGraph,\n serviceName: string,\n channel: string,\n): string {\n const id = websocketChannelId(serviceName, channel)\n if (graph.hasNode(id)) return id\n const node: WebSocketChannelNode = {\n id,\n type: NodeType.WebSocketChannelNode,\n name: channel,\n service: serviceName,\n channel,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// A messaging destination node (Kafka topic, queue, stream) keyed exactly the\n// way the static extractor keys it, so the OBSERVED and EXTRACTED edges fuse\n// onto one node instead of twinning. The Kafka static side names its topic\n// `infra:kafka-topic:<topic>` (extract/calls/kafka.ts), so the kind is\n// `<messaging.system>-topic` — `kafka` → `kafka-topic` — and the same shape\n// generalises to every messaging system the semconv names. `provider: 'self'`\n// mirrors the static extractor's non-AWS provider so an observed-first node\n// merges cleanly when static analysis later reaches the same destination.\nfunction messagingDestinationKind(system: string): string {\n return `${system}-topic`\n}\n\nfunction ensureMessagingDestinationNode(\n graph: NeatGraph,\n system: string,\n destination: string,\n): string {\n const id = infraId(messagingDestinationKind(system), destination)\n if (graph.hasNode(id)) return id\n const node: InfraNode = {\n id,\n type: NodeType.InfraNode,\n name: destination,\n provider: 'self',\n kind: messagingDestinationKind(system),\n }\n graph.addNode(id, node)\n return id\n}\n\n// Parent-span TTL cache (ADR-033). Address-based peer resolution (server.address /\n// net.peer.name / url.full) misses non-HTTP RPCs and any span with an opaque\n// peer. The cache stores each span's service keyed by `${traceId}:${spanId}` so\n// a child span whose address resolution fails can fall back to its parent's\n// service, identifying a cross-service CALLS edge from parent → current.\n//\n// Bounded size + TTL — out-of-order arrival (child before parent) drops the\n// child rather than buffering. We accept that loss because the cache is best-\n// effort: for every cross-service call, the CLIENT span on the caller side\n// covers the same edge via address-based resolution, so missing one direction\n// is recoverable.\nconst PARENT_SPAN_CACHE_SIZE = 10_000\nconst PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1000\n\ninterface ParentSpanCacheEntry {\n service: string\n // Env discriminator from the parent span (ADR-074 §2). The parent-span\n // fallback in handleSpan uses this so the auto-created parent ServiceNode\n // lands on the same env-tagged id the OTel emitter advertised.\n env: string\n // The parent span's own `code.*` call site, when its SpanProcessor captured\n // one (file-awareness.md §4). The parent-span fallback below originates its\n // edge from the parent's FileNode instead of the bare parent ServiceNode when\n // this is present, so the fallback edge anchors to file:line rather than\n // pinning to a service node (issue #536). Undefined when the parent carried no\n // call site — never fabricated (§6), so the service-level fallback stands.\n callSite?: CallSite\n expiresAt: number\n}\n\nconst parentSpanCache = new Map<string, ParentSpanCacheEntry>()\n\nfunction parentSpanKey(traceId: string, spanId: string): string {\n return `${traceId}:${spanId}`\n}\n\nfunction cacheSpanService(span: ParsedSpan, now: number, callSite: CallSite | null): void {\n if (!span.traceId || !span.spanId) return\n const key = parentSpanKey(span.traceId, span.spanId)\n // Map preserves insertion order, so deleting + re-inserting bumps an entry to\n // the back. Eviction is \"drop oldest\" once size exceeds the cap.\n parentSpanCache.delete(key)\n parentSpanCache.set(key, {\n service: span.service,\n env: span.env ?? 'unknown',\n ...(callSite ? { callSite } : {}),\n expiresAt: now + PARENT_SPAN_CACHE_TTL_MS,\n })\n while (parentSpanCache.size > PARENT_SPAN_CACHE_SIZE) {\n const oldest = parentSpanCache.keys().next().value\n if (!oldest) break\n parentSpanCache.delete(oldest)\n }\n}\n\nfunction lookupParentSpan(\n traceId: string,\n parentSpanId: string,\n now: number,\n): { service: string; env: string; callSite?: CallSite } | null {\n const entry = parentSpanCache.get(parentSpanKey(traceId, parentSpanId))\n if (!entry) return null\n if (entry.expiresAt <= now) {\n parentSpanCache.delete(parentSpanKey(traceId, parentSpanId))\n return null\n }\n return {\n service: entry.service,\n env: entry.env,\n ...(entry.callSite ? { callSite: entry.callSite } : {}),\n }\n}\n\n// Test seam: lets unit tests start from a clean slate.\nexport function resetParentSpanCache(): void {\n parentSpanCache.clear()\n}\n\n// Peer host → ServiceNode id resolution. With env-dimension (ADR-074 §2),\n// the same `name` may live across multiple ServiceNodes — one per env, plus\n// the env-less form from static extraction. When `env` is known (the source\n// span's env), prefer a same-env match; fall back to the env-less node so\n// EXTRACTED edges from static analysis remain reachable until OBSERVED\n// traffic from the same env promotes them.\n//\n// Match passes:\n// 1. Exact id lookup for `(host, env)` — `serviceId(host, env)`.\n// 2. Exact id lookup for env-less `serviceId(host)`.\n// 3. Name/alias scan across every ServiceNode, preferring same-env then\n// env-less then any other env.\nfunction resolveServiceId(\n graph: NeatGraph,\n host: string,\n env: string,\n): string | null {\n const envTagged = serviceId(host, env)\n if (graph.hasNode(envTagged)) return envTagged\n const envLess = serviceId(host)\n if (envLess !== envTagged && graph.hasNode(envLess)) return envLess\n\n let sameEnv: string | null = null\n let envLessMatch: string | null = null\n let anyMatch: string | null = null\n graph.forEachNode((id, attrs) => {\n if (sameEnv) return\n const a = attrs as ServiceNode & { type?: string }\n if (a.type !== NodeType.ServiceNode) return\n const matchesByName = a.name === host\n const matchesByAlias = a.aliases ? a.aliases.includes(host) : false\n if (!matchesByName && !matchesByAlias) return\n const nodeEnv = a.env ?? 'unknown'\n if (nodeEnv === env) {\n sameEnv = id\n return\n }\n if (nodeEnv === 'unknown' && !envLessMatch) envLessMatch = id\n else if (!anyMatch) anyMatch = id\n })\n return sameEnv ?? envLessMatch ?? anyMatch\n}\n\nexport function frontierIdFor(host: string): string {\n return frontierId(host)\n}\n\n// Auto-create a minimal ServiceNode for span.service when no such node exists.\n// Used at the top of handleSpan so subsequent edge upserts always have endpoints\n// — without it, OBSERVED edges silently drop for any service the static\n// extractor hasn't reached yet (and never reaches at all in OTel-only setups).\n// `language: 'unknown'` is the contract's specified placeholder (ADR-033). When\n// static extraction later produces a ServiceNode at the same id, addServiceNodes\n// merges and flips discoveredVia to 'merged' rather than overwriting.\n// Exported for the connectors plane (connectors/index.ts) — a connector\n// signal needs the same auto-vivified ServiceNode any OTel span gets before\n// an edge upsert, since a connector-sourced service may never have been\n// statically extracted or seen a span yet.\nexport function ensureServiceNode(\n graph: NeatGraph,\n serviceName: string,\n env: string,\n): string {\n const id = serviceId(serviceName, env)\n if (graph.hasNode(id)) return id\n const node: ServiceNode = {\n id,\n type: NodeType.ServiceNode,\n name: serviceName,\n language: 'unknown',\n discoveredVia: 'otel',\n ...(env !== 'unknown' ? { env } : {}),\n }\n graph.addNode(id, node)\n return id\n}\n\n// Same shape for unseen db.system + host pairs. Engine comes off the OTel\n// attribute as a string per Rule 8 — no hardcoded engine list. compatibleDrivers\n// is empty until static extraction merges in the matrix-derived drivers.\nfunction ensureDatabaseNode(graph: NeatGraph, host: string, engine: string): string {\n const id = databaseId(host)\n if (graph.hasNode(id)) return id\n const node: DatabaseNode = {\n id,\n type: NodeType.DatabaseNode,\n name: host,\n engine,\n engineVersion: 'unknown',\n compatibleDrivers: [],\n host,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// An in-process / embedded database (SQLite, better-sqlite3, an in-memory\n// store) crosses no network boundary, so its span carries no peer host to key a\n// DatabaseNode on. Key it on a service-scoped local identity instead so two\n// services each reading their own `app.db` stay distinct nodes rather than\n// collapsing onto one (ADR-118). `name` is the logical database from the span\n// (db.name) when present, the engine string otherwise. `host` is intentionally\n// omitted — an embedded database has no network host, and evidence is never\n// fabricated (file-awareness.md §6); host-mismatch divergence skips a hostless\n// DatabaseNode. Returns the node id so the caller can point the CONNECTS_TO edge\n// at it. Idempotent — high-volume DB spans upsert, never grow the node set.\nfunction ensureLocalDatabaseNode(\n graph: NeatGraph,\n serviceName: string,\n name: string,\n engine: string,\n): string {\n const id = localDatabaseId(serviceName, name)\n if (graph.hasNode(id)) return id\n const node: DatabaseNode = {\n id,\n type: NodeType.DatabaseNode,\n name,\n engine,\n engineVersion: 'unknown',\n compatibleDrivers: [],\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\nfunction ensureFrontierNode(graph: NeatGraph, host: string, ts: string): string {\n const id = frontierIdFor(host)\n if (graph.hasNode(id)) {\n const existing = graph.getNodeAttributes(id) as FrontierNode\n graph.replaceNodeAttributes(id, { ...existing, lastObserved: ts })\n return id\n }\n const node: FrontierNode = {\n id,\n type: NodeType.FrontierNode,\n name: host,\n host,\n firstObserved: ts,\n lastObserved: ts,\n }\n graph.addNode(id, node)\n return id\n}\n\n// Exported so the connectors plane (connectors/index.ts) mints edges through\n// the identical primitive OTel ingest uses — a connector-sourced edge and a\n// span-sourced edge are meant to be indistinguishable to traversal,\n// divergence, and staleness (docs/connectors/README.md).\nexport interface UpsertResult {\n edge: GraphEdge\n created: boolean\n}\n\nexport function upsertObservedEdge(\n graph: NeatGraph,\n type: EdgeTypeValue,\n source: string,\n target: string,\n ts: string,\n isError = false,\n evidence?: { file: string; line?: number },\n): UpsertResult | null {\n if (!graph.hasNode(source) || !graph.hasNode(target)) return null\n\n const id = makeObservedEdgeId(type, source, target)\n if (graph.hasEdge(id)) {\n const existing = graph.getEdgeAttributes(id) as GraphEdge\n const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1\n const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0)\n const newSignal = {\n spanCount: newSpanCount,\n errorCount: newErrorCount,\n lastObservedAgeMs: 0,\n }\n // ADR-066 §2 — confidence grades from the signal block. PROV_RANK stays;\n // the grade reflects volume + recency + error ratio within the OBSERVED\n // tier.\n const updated: GraphEdge = {\n ...existing,\n provenance: Provenance.OBSERVED,\n lastObserved: ts,\n callCount: newSpanCount,\n signal: newSignal,\n confidence: confidenceForObservedSignal(newSignal),\n }\n graph.replaceEdgeAttributes(id, updated)\n return { edge: updated, created: false }\n }\n\n const signal = {\n spanCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedAgeMs: 0,\n }\n const edge: GraphEdge = {\n id,\n source,\n target,\n type,\n provenance: Provenance.OBSERVED,\n confidence: confidenceForObservedSignal(signal),\n lastObserved: ts,\n callCount: 1,\n signal,\n // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).\n // Only set when code.filepath was present on the span — never fabricated.\n ...(evidence ? { evidence } : {}),\n }\n graph.addEdgeWithKey(id, source, target, edge)\n return { edge, created: true }\n}\n\n// When a span errors, the system is exercising its dependencies right now even\n// if some of them aren't auto-instrumented (pg 7.4.0 in the demo, see ADR-014).\n// Walk EXTRACTED edges out from the erroring service for a couple of hops and\n// promote them to INFERRED twins so traversal can prefer them over the bare\n// static edges without claiming OBSERVED-grade certainty.\nfunction stitchTrace(graph: NeatGraph, sourceServiceId: string, ts: string): void {\n if (!graph.hasNode(sourceServiceId)) return\n\n const visited = new Set<string>([sourceServiceId])\n const queue: { nodeId: string; depth: number }[] = [{ nodeId: sourceServiceId, depth: 0 }]\n\n while (queue.length > 0) {\n const { nodeId, depth } = queue.shift()!\n if (depth >= STITCH_MAX_DEPTH) continue\n\n const outbound = graph.outboundEdges(nodeId)\n for (const edgeId of outbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) continue\n\n // Only runtime dependency edges get stitched. Structural edges (CONTAINS /\n // IMPORTS / CONFIGURED_BY / RUNS_ON) are never mirrored into INFERRED twins\n // and the BFS does not recurse through them — an error propagates along\n // dependencies, not static containment (trace-stitcher.md allowlist).\n if (!STITCH_EDGE_TYPES.has(edge.type)) continue\n\n // OBSERVED twin already covers this hop with ground truth — no inference\n // needed (ADR-034). Stomping it with INFERRED erases the gap NEAT exists\n // to surface; skipping it keeps the OBSERVED edge as the authoritative\n // record and avoids cluttering the graph with a redundant INFERRED twin.\n if (graph.hasEdge(observedEdgeId(edge.source, edge.target, edge.type))) continue\n\n upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts)\n\n if (!visited.has(edge.target)) {\n visited.add(edge.target)\n queue.push({ nodeId: edge.target, depth: depth + 1 })\n }\n }\n }\n}\n\nfunction upsertInferredEdge(\n graph: NeatGraph,\n type: EdgeTypeValue,\n source: string,\n target: string,\n ts: string,\n): void {\n const id = makeInferredEdgeId(type, source, target)\n if (graph.hasEdge(id)) {\n const existing = graph.getEdgeAttributes(id) as GraphEdge\n const updated: GraphEdge = { ...existing, lastObserved: ts }\n graph.replaceEdgeAttributes(id, updated)\n return\n }\n\n const edge: GraphEdge = {\n id,\n source,\n target,\n type,\n provenance: Provenance.INFERRED,\n confidence: INFERRED_CONFIDENCE,\n lastObserved: ts,\n }\n graph.addEdgeWithKey(id, source, target, edge)\n}\n\nasync function appendErrorEvent(ctx: IngestContext, ev: ErrorEvent): Promise<void> {\n await fs.mkdir(path.dirname(ctx.errorsPath), { recursive: true })\n await fs.appendFile(ctx.errorsPath, JSON.stringify(ev) + '\\n', 'utf8')\n}\n\n// Resolve the incident's affectedNode. When the span carries a `code.filepath`\n// call site, the incident attributes to the FileNode the failure surfaced in —\n// the same file grain OBSERVED CALLS edges land on (file-awareness.md §4) —\n// resolving a compiled `dist/...js` frame through its disk-adjacent source map\n// when one is present. Without a call site it stays at the originating service,\n// the honest fallback (§2).\n//\n// The runtime `code.filepath` is a deploy-absolute path (`/var/task/...` on\n// Lambda, `/app/...` in a container image) that need not match the daemon's\n// checkout. When the graph is available it's reconciled onto the service-\n// relative path the extractor already minted (reconcileObservedRelPath, the\n// same trailing-suffix match the OBSERVED edge origin uses), so the incident\n// lands on the ONE fused FileNode instead of a phantom keyed off the absolute\n// path — the node root-cause actually walks. Without a graph the honest runtime\n// path stands: the file node may not be materialised yet, but querying the\n// service still surfaces the incident, and the file:line is real (§6 — never\n// fabricated).\nfunction incidentAffectedNode(\n span: ParsedSpan,\n graph?: NeatGraph,\n scanPath?: string,\n): string {\n const sid = serviceId(span.service, span.env)\n const serviceNode =\n graph && graph.hasNode(sid)\n ? (graph.getNodeAttributes(sid) as ServiceNode)\n : undefined\n const callSite = callSiteFromSpan(span, serviceNode, scanPath)\n if (callSite) {\n const relPath = graph\n ? reconcileObservedRelPath(graph, span.service, callSite.relPath)\n : callSite.relPath\n return fileId(span.service, relPath)\n }\n return sid\n}\n\n// Build the minimal ErrorEvent the receiver writes synchronously before\n// replying (ADR-033 §Error events, amended). affectedNode attributes to the\n// FileNode when the span carries a `code.filepath` call site, else to the\n// originating service (incidentAffectedNode above).\n//\n// errorMessage reads from the exception event's `exception.message` (OTel\n// semconv) so the incident surface shows the actual thrown error string.\n// When the span carries no exception event the field falls back to the HTTP\n// context the span still holds — \"500 on GET /users/:id\" (httpFailureMessage)\n// — and only then to the literal 'unknown error'. `span.name` is never in the\n// chain: OTel HTTP server instrumentation routinely populates it with the HTTP\n// method, which produces incidents that read 'GET' or 'POST' instead of the\n// underlying failure. `span.status.message` is intentionally out for the same\n// reason.\n// Span attributes pass through verbatim so consumers can read source\n// attribution (`code.filepath`, `code.lineno`, `code.function`) and other\n// SDK-emitted context without ingest enumerating every key it cares about.\n// Coerce span attributes to a JSON-safe shape — bigint values from the\n// parsed span (long ids, high-cardinality counters) become strings so the\n// passthrough record can be serialised to the ErrorEvent shape and round-\n// tripped through ErrorEventSchema. All other types pass through verbatim.\nfunction sanitizeAttributes(\n attrs: ParsedSpan['attributes'],\n): Record<string, string | number | boolean | null | string[] | number[] | boolean[]> {\n const out: Record<string, string | number | boolean | null | string[] | number[] | boolean[]> = {}\n for (const [k, v] of Object.entries(attrs)) {\n if (typeof v === 'bigint') out[k] = v.toString()\n else out[k] = v as string | number | boolean | null | string[] | number[] | boolean[]\n }\n return out\n}\n\nexport function buildErrorEventForReceiver(\n span: ParsedSpan,\n graph?: NeatGraph,\n scanPath?: string,\n): ErrorEvent | null {\n if (span.statusCode !== 2) return null\n const ts = span.startTimeIso ?? new Date().toISOString()\n const attrs = sanitizeAttributes(span.attributes)\n return {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode: incidentAffectedNode(span, graph, scanPath),\n }\n}\n\n// Synchronous file-write helper bound to a receiver. The receiver awaits this\n// before replying, so a write failure surfaces as 500 → OTel SDK retries.\nexport function makeErrorSpanWriter(\n errorsPath: string,\n graph?: NeatGraph,\n scanPath?: string,\n): (span: ParsedSpan) => Promise<void> {\n return async (span) => {\n const ev = buildErrorEventForReceiver(span, graph, scanPath)\n if (!ev) return\n await fs.mkdir(path.dirname(errorsPath), { recursive: true })\n await fs.appendFile(errorsPath, JSON.stringify(ev) + '\\n', 'utf8')\n }\n}\n\n// Write one failing-response incident (issue #481) to errors.ndjson. Used for\n// an unambiguous 5xx (count 1) and for a flushed 4xx burst (count N). The\n// dominant status code names the failure; `incidentCount` carries N so the\n// incident surface shows \"5× 404\" without the per-span flood. Span attributes\n// pass through verbatim, same as the statusCode === 2 path, so source\n// attribution (`code.*`) and the response code survive to the consumer.\nasync function recordFailingResponseIncident(\n ctx: IngestContext,\n span: ParsedSpan,\n affectedNode: string,\n timestamp: string,\n statusCode: number,\n count: number,\n firstTimestamp?: string,\n): Promise<void> {\n const attrs = sanitizeAttributes(span.attributes)\n const first = firstTimestamp ?? timestamp\n const peer = pickAddress(span)\n const message =\n count > 1\n ? `${count} consecutive HTTP ${statusCode} responses` +\n (peer ? ` to ${peer}` : '')\n : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : '')\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorType: 'http-failure',\n errorMessage: message,\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode,\n httpStatusCode: statusCode,\n incidentCount: count,\n firstTimestamp: first,\n lastTimestamp: timestamp,\n }\n await appendErrorEvent(ctx, ev)\n}\n\n// Record one incident for a span that carries an exception event but no ERROR\n// status and no HTTP failure code — an async / queue / background worker\n// (bullmq, Redis Streams, a scheduled task) whose job threw (ADR-117). Incident\n// recording keys on the failure signal, not on HTTP context: the message\n// follows the shared incidentMessage chain and the record attributes to the\n// handler file:line when the span carries `code.filepath`, else to the\n// originating service (incidentAffectedNode). handleSpan owns this write — the\n// receiver's synchronous error-writer fires only for statusCode === 2, so an\n// exception-only worker span reaches durability here, the same way the\n// response-code incidents below do.\nasync function recordExceptionIncident(\n ctx: IngestContext,\n span: ParsedSpan,\n ts: string,\n): Promise<void> {\n const attrs = sanitizeAttributes(span.attributes)\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath),\n }\n await appendErrorEvent(ctx, ev)\n}\n\n// Advance the 4xx burst for this (source, peer) pair (issue #481). A burst\n// accumulates silently; only when it crosses the threshold inside the window\n// does it flush ONE coalesced incident. A 4xx that arrives more than windowMs\n// after the previous one resets the burst — a slow trickle of probes never\n// coalesces. The dominant code is the most frequent 4xx seen across the burst.\nasync function advance4xxBurst(\n ctx: IngestContext,\n span: ParsedSpan,\n affectedNode: string,\n ts: string,\n nowMs: number,\n status: number,\n): Promise<void> {\n const { threshold, windowMs } = loadIncidentThresholdsFromEnv()\n if (!ctx.burstState) ctx.burstState = new Map()\n const peer = pickAddress(span) ?? span.spanId\n const key = `${span.service}->${peer}`\n const existing = ctx.burstState.get(key)\n let state: BurstState\n if (existing && nowMs - existing.lastMs <= windowMs) {\n existing.count += 1\n existing.lastTs = ts\n existing.lastMs = nowMs\n existing.codes.set(status, (existing.codes.get(status) ?? 0) + 1)\n state = existing\n } else {\n state = {\n count: 1,\n firstTs: ts,\n lastTs: ts,\n lastMs: nowMs,\n codes: new Map([[status, 1]]),\n }\n ctx.burstState.set(key, state)\n }\n\n if (state.count < threshold) return\n\n // Threshold met — flush one incident carrying the count, the dominant code,\n // and the burst's first/last timestamps, then clear the burst so the next\n // run of failures records its own incident rather than re-flushing every span.\n let dominant = status\n let max = 0\n for (const [code, n] of state.codes) {\n if (n > max) {\n max = n\n dominant = code\n }\n }\n await recordFailingResponseIncident(\n ctx,\n span,\n affectedNode,\n state.lastTs,\n dominant,\n state.count,\n state.firstTs,\n )\n ctx.burstState.delete(key)\n}\n\nexport async function handleSpan(ctx: IngestContext, span: ParsedSpan): Promise<void> {\n // lastObserved derives from the span's own startTime per ADR-033 — replayed\n // traces and out-of-order spans get a timestamp that reflects when the call\n // actually fired, not when the receiver received it. Wall-clock is only the\n // fallback for spans whose startTimeUnixNano is missing or unparseable.\n const ts = span.startTimeIso ?? nowIso(ctx)\n const nowMs = ctx.now ? ctx.now() : Date.now()\n // Env discriminator from `deployment.environment(.name)` (ADR-074 §2).\n // Older ParsedSpan producers may omit it — fall back to the literal\n // `'unknown'` so the env-less wire format is preserved on auto-creation.\n const env = span.env ?? 'unknown'\n // Issue #374 — spans whose resource omits `service.name` route to\n // `service:unidentified` in the URL-resolved project (the parser already\n // substitutes the fallback). One warning per project per session names\n // the project so an operator can fix the SDK config without grepping.\n if (span.resourceServiceNamePresent === false) {\n warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT)\n }\n // Auto-create a minimal ServiceNode for unseen span.service so OBSERVED\n // edges land instead of silently dropping. Static extraction merges richer\n // fields when it later finds the same id (ADR-033). The node is env-tagged\n // when the span carries an env signal.\n const sourceId = ensureServiceNode(ctx.graph, span.service, env)\n const isError = span.statusCode === 2\n\n // File-first OBSERVED origin (file-awareness.md §4). When the injected\n // SpanProcessor captured a call site on this outbound (CLIENT/PRODUCER) span,\n // the relationship originates from the file; without one it stays\n // service-level. `observedSource()` creates the FileNode + CONTAINS lazily so\n // they only land when an edge actually does — and never for the inbound\n // (SERVER) parent-fallback side, which carries no call site.\n const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId) as ServiceNode\n const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath)\n\n // Stash this span in the parent-span cache so any later child whose address\n // resolution misses can still resolve the cross-service edge via parentSpanId.\n // The call site rides along so the fallback edge anchors to this span's\n // file:line when this span turns out to be a parent (issue #536).\n cacheSpanService(span, nowMs, callSite)\n const observedSource = (): string =>\n callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId\n // Evidence for the OBSERVED edge — populated from the span's code.* semconv\n // when the call site resolved (file-awareness.md §4 + §6). Never fabricated:\n // absent call site → undefined evidence. The path is reconciled the same way\n // the edge's origin node is (reconcileObservedRelPath), so evidence.file names\n // the fused EXTRACTED path the edge lands on rather than the raw deployed\n // absolute path — otherwise the edge node and its own evidence disagree.\n const callSiteEvidence: { file: string; line?: number } | undefined = callSite\n ? {\n file: reconcileObservedRelPath(ctx.graph, span.service, callSite.relPath),\n ...(callSite.line !== undefined ? { line: callSite.line } : {}),\n }\n : undefined\n\n let affectedNode = sourceId\n\n // Only the caller/producer side of a call mints an OBSERVED edge directly\n // (issue #429). INTERNAL / SERVER / CONSUMER spans don't: a SERVER/CONSUMER\n // span is the callee, and its edge is minted from its parent via the\n // parent-span fallback below (left ungated). Gating here keeps INTERNAL\n // connection spans (`tcp.connect` / `tls.connect` with a peer address) from\n // minting spurious service-level edges.\n const mintsFromCallerSide = spanMintsObservedEdge(span.kind)\n\n if (span.dbSystem) {\n // Database span. A networked database resolves its DatabaseNode by peer\n // host; an in-process / embedded one (SQLite, better-sqlite3, an in-memory\n // store) crosses no network boundary and carries no peer address, so it\n // keys on a service-scoped local identity instead (ADR-118). Both mint the\n // same file-grained service→database CONNECTS_TO OBSERVED edge — the edge\n // that makes a leaf service's datastore reads legible (#576, #546).\n if (mintsFromCallerSide) {\n const host = pickAddress(span)\n // Engine comes off the OTel attribute as a string per Rule 8 — no\n // hardcoded engine list on either branch.\n let targetId: string\n if (host) {\n ensureDatabaseNode(ctx.graph, host, span.dbSystem)\n targetId = databaseId(host)\n } else {\n // No peer host — an in-process DB. Name the node by its logical\n // database (db.name) when the span carries one, the engine otherwise.\n const localName = span.dbName ?? span.dbSystem\n targetId = ensureLocalDatabaseNode(\n ctx.graph,\n span.service,\n localName,\n span.dbSystem,\n )\n }\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONNECTS_TO,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n }\n } else if (\n span.messagingSystem &&\n span.messagingDestination &&\n spanMintsMessagingEdge(span.kind)\n ) {\n // Messaging span. The semantic destination is the topic / queue / stream the\n // code talks to — not the broker host, which is transport. A PRODUCER span\n // publishes; a CONSUMER span consumes. Both mint an OBSERVED edge to the same\n // destination node the static extractor names (extract/calls/kafka.ts), so\n // the declared and observed queue edges fuse into one (→ divergence) instead\n // of twinning. File-grained through the same call-site path as any other\n // OBSERVED edge (file-awareness.md §4): when the span carries `code.*`, the\n // edge originates from the caller's FileNode at the exact call site,\n // reconciled onto the EXTRACTED service-relative path (`reconcileObservedRelPath`,\n // ADR-118); without a call site it stays service-level, honestly. This is the\n // consumer-side pair of the producer edge — the queue topology the OBSERVED\n // layer used to leave dark on the consumer side (issue #614).\n const targetId = ensureMessagingDestinationNode(\n ctx.graph,\n span.messagingSystem,\n span.messagingDestination,\n )\n const edgeType =\n span.kind === WIRE_SPAN_KIND_CONSUMER\n ? EdgeType.CONSUMES_FROM\n : EdgeType.PUBLISHES_TO\n const result = upsertObservedEdge(\n ctx.graph,\n edgeType,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (\n span.graphqlOperationName &&\n span.graphqlOperationType &&\n spanServesGraphqlOperation(span.kind)\n ) {\n // GraphQL execution span (issue #615). Every GraphQL request rides one HTTP\n // endpoint (`POST /graphql`), so at HTTP grain the whole API collapses to a\n // single edge and the operation-level topology is invisible. The execution\n // span carries the operation the client actually named — `graphql.operation.name`\n // with `graphql.operation.type` — so mint an OBSERVED `CONTAINS` edge from the\n // serving service to a per-operation node, the same ownership shape a service\n // has over a route (ADR-119) and a file (file-awareness.md §2). OBSERVED-only:\n // the SDL / resolver map is not parsed statically in this cut; the node is\n // minted observed-first and a future static GraphQL extractor fuses onto the\n // same id (ADR-122). File-grained through the same call-site path as any other\n // OBSERVED edge (file-awareness.md §4): when the span carries `code.*` (the\n // resolver call site) the edge originates from that `FileNode` at the exact\n // `file:line`, reconciled onto the EXTRACTED service-relative path; without a\n // call site it stays service-level, honestly. Both operation name and type\n // must be present to key a stable id — a nameless/typeless execution span\n // falls through rather than minting a fabricated operation.\n const targetId = ensureGraphqlOperationNode(\n ctx.graph,\n span.service,\n span.graphqlOperationType,\n span.graphqlOperationName,\n )\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONTAINS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (\n span.rpcSystem === 'grpc' &&\n span.rpcService &&\n span.rpcMethod &&\n spanServesGrpcMethod(span.kind)\n ) {\n // gRPC execution span (issue #616). gRPC used to engage only at service\n // grain: every method collapsed onto one service→service edge, so the\n // per-method topology was invisible and one-sided. The serving span carries\n // the method the caller actually invoked — `rpc.service` (the fully-qualified\n // `orders.OrderService`) with `rpc.method` (`GetOrder`) — so mint an OBSERVED\n // `CONTAINS` edge from the serving service to a per-method node, the same\n // ownership shape a service has over a route (ADR-119), a GraphQL operation\n // (ADR-122), and a file (file-awareness.md §2). The node is keyed on the\n // fully-qualified `rpc.service` — the wire contract both the span and the\n // `.proto` carry verbatim — so the static `.proto` extractor's declared\n // method fuses onto this same id into a two-sided divergence (ADR-123).\n // File-grained through the same call-site path as any other OBSERVED edge\n // (file-awareness.md §4): when the span carries `code.*` (the handler call\n // site) the edge originates from that `FileNode` at the exact `file:line`,\n // reconciled onto the EXTRACTED service-relative path; without a call site it\n // stays service-level, honestly. The gate admits only the serving side —\n // SERVER / INTERNAL / unkinded — so a CLIENT span mints no ownership\n // (client→method attribution is deferred) and instead falls through to the\n // cross-service resolver, leaving the caller→callee edge intact. Both service\n // and method must be present to key a stable id.\n const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod)\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONTAINS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {\n // WebSocket upgrade span (issue #617). A WebSocket app used to produce no\n // OBSERVED topology at all: only message-handler errors surfaced, as\n // incidents, and the channels themselves stayed invisible — the frames after\n // the handshake ride the socket, not more spans. The one span that reliably\n // marks a channel is the HTTP upgrade that opens it: a SERVER `GET` carrying\n // `Upgrade: websocket` and the connection path. So mint an OBSERVED\n // `CONNECTS_TO` edge from the serving service to a per-channel node —\n // reusing the same connection edge a service has to a datastore (#576), not a\n // new edge type. Unlike the structural `CONTAINS` a service has over a route /\n // operation / method — durably declared artifacts whose edge never goes\n // stale — a channel's whole meaning is liveness, so `CONNECTS_TO` is the right\n // shape: it carries `lastObserved` and decays OBSERVED → STALE on\n // CONNECTS_TO's own threshold when the channel goes quiet (the daemon\n // staleness loop, #532). OBSERVED-only: a WebSocket channel is known from\n // observation, never from static extraction, so the node has no declared twin\n // and is excluded from `missing-extracted` (divergences.ts). File-grained\n // through the same call-site path as any other OBSERVED edge\n // (file-awareness.md §4): when the span carries `code.*` the edge originates\n // from that `FileNode` at the exact `file:line`, reconciled onto the EXTRACTED\n // service-relative path; without a call site it stays service-level, honestly.\n // The gate admits only the serving side — SERVER / INTERNAL / unkinded — so a\n // CLIENT upgrade span mints no channel (client→channel attribution is\n // deferred, ADR-125).\n const targetId = ensureWebsocketChannelNode(\n ctx.graph,\n span.service,\n span.websocketChannel,\n )\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONNECTS_TO,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else {\n // Possibly a cross-service call. Resolve the peer; if it matches a known\n // ServiceNode, record an OBSERVED CALLS edge to the typed target. If it\n // matches nothing — pod IP, ingress hostname, AWS PrivateLink endpoint —\n // create a FrontierNode placeholder and record an OBSERVED edge to that\n // FrontierNode so the call carries the same provenance + signal-block +\n // graded confidence as any other OBSERVED edge (ADR-068). The target ref\n // identifies the node-type; provenance describes how the edge was learned.\n // promoteFrontierNodes (run by the extract orchestrator) rewrites the\n // target ref once a later round resolves the host; the edge's provenance\n // stays OBSERVED across promotion.\n // A loopback host (localhost / 127.0.0.0/8 / ::1) is skipped here: it never\n // resolves to a distinct peer, and minting frontier:localhost would double\n // the edge that the callee's parent-span fallback already records for this\n // same call (issues #590, #577). Leaving resolvedViaAddress false hands the\n // call to that fallback instead.\n const host = pickAddress(span)\n let resolvedViaAddress = false\n if (mintsFromCallerSide && host && host !== span.service && !isLoopbackHost(host)) {\n const targetId = resolveServiceId(ctx.graph, host, env)\n if (targetId && targetId !== sourceId) {\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n affectedNode = targetId\n resolvedViaAddress = true\n } else if (!targetId) {\n const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts)\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n frontierNodeId,\n ts,\n isError,\n callSiteEvidence,\n )\n affectedNode = frontierNodeId\n resolvedViaAddress = true\n }\n }\n\n // Parent-span fallback (ADR-033): when address-based resolution didn't\n // produce an edge and the span has a parentSpanId we've cached, the\n // parent's service identifies the caller. The current span is the server\n // side of the call, so the edge direction is parent.service → current.\n // The cached entry carries the parent span's env, so the auto-created\n // parent ServiceNode lands on the env-tagged id the parent advertised.\n if (!resolvedViaAddress && span.parentSpanId) {\n const parent = lookupParentSpan(span.traceId, span.parentSpanId, nowMs)\n if (parent && parent.service !== span.service) {\n const parentId = ensureServiceNode(ctx.graph, parent.service, parent.env)\n // When the parent span carried a `code.*` call site, originate the edge\n // from the parent's FileNode so it anchors to file:line instead of the\n // bare parent ServiceNode (issue #536). Without a cached call site the\n // edge stays service-coarse — never fabricated (file-awareness.md §6).\n const fallbackSource = parent.callSite\n ? ensureObservedFileNode(ctx.graph, parent.service, parentId, parent.callSite)\n : parentId\n const fallbackEvidence: { file: string; line?: number } | undefined =\n parent.callSite\n ? {\n file: reconcileObservedRelPath(\n ctx.graph,\n parent.service,\n parent.callSite.relPath,\n ),\n ...(parent.callSite.line !== undefined\n ? { line: parent.callSite.line }\n : {}),\n }\n : undefined\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n fallbackSource,\n sourceId,\n ts,\n isError,\n fallbackEvidence,\n )\n }\n }\n }\n\n if (span.statusCode === 2) {\n stitchTrace(ctx.graph, sourceId, ts)\n // The durable ErrorEvent write moved to the receiver so the file write\n // happens synchronously before the 200 reply (ADR-033 §Error events,\n // amended). watch.ts wires makeErrorSpanWriter into onErrorSpanSync.\n // handleSpan still runs the in-graph error effects (stitchTrace above);\n // it just doesn't append to errors.ndjson anymore. ctx.errorsPath stays\n // for the optional opt-in path below — daemon-less callers (CLI tests,\n // ad-hoc scripts) that skip the receiver hook still get a write here.\n if (ctx.writeErrorEventInline !== false) {\n const attrs = sanitizeAttributes(span.attributes)\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode,\n }\n await appendErrorEvent(ctx, ev)\n }\n }\n\n // Failing-response incidents (issue #481). OTel semconv leaves a CLIENT span's\n // status UNSET on a 4xx/5xx response, so the status-only path above is blind\n // to a service whose outbound calls are failing en masse — the exact gap a\n // debugging session hits (80× HTTP 404 against one peer surfacing nothing).\n // A response status is read from the span here regardless of statusCode:\n // * 5xx → record an incident immediately (unambiguous failure, even with\n // UNSET status). Skipped when statusCode === 2 already recorded above so\n // a 5xx that also carries ERROR status isn't double-counted.\n // * 4xx on a CLIENT/PRODUCER span → coalesce. The burst against this\n // (source, peer) pair advances; when it reaches the threshold inside the\n // window it records ONE incident carrying the count and dominant code.\n // * a lone 4xx, or any 2xx/3xx → no incident.\n // Always written here (not gated on writeErrorEventInline): the daemon's\n // receiver only fires its synchronous error-writer for statusCode === 2, so\n // these spans never reach that durability handoff — handleSpan owns them.\n if (span.statusCode !== 2) {\n const status = httpResponseStatus(span)\n // ADR-117 — an exception event on a span that left its status UNSET is\n // still an unambiguous failure: a bullmq / Redis-Streams / background\n // worker whose job threw carries the exception and no HTTP response\n // context, so the status-only and response-code paths both miss it. Record\n // it independent of HTTP, attributed to the handler file:line (or the\n // service) the same way the statusCode === 2 path is. Ordered first because\n // the exception is the unambiguous signal; the response-code branches below\n // carry the exception-less HTTP failures.\n if (span.exception) {\n await recordExceptionIncident(ctx, span, ts)\n } else if (status !== undefined && status >= 500) {\n // A failing-response incident is attributed to the SOURCE service — the\n // caller whose outbound calls are failing is the node a debugger asks\n // about (\"why is my service erroring\"). The peer it failed against is\n // carried in the message and in attributes. This is deliberately not the\n // edge target (frontier/peer) the OBSERVED edge above resolved to: the\n // signal is \"this service's calls to X are failing\", not \"X failed\".\n await recordFailingResponseIncident(ctx, span, sourceId, ts, status, 1)\n } else if (\n status !== undefined &&\n status >= 400 &&\n spanMintsObservedEdge(span.kind)\n ) {\n await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status)\n }\n }\n void affectedNode\n\n // Post-ingest policy trigger (ADR-043). The hook is awaited so failures\n // surface; daemons wrap it in a try/catch that logs without throwing.\n if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph)\n}\n\nexport { stitchTrace }\n\n// Promote any frontier:<host> placeholder whose host matches an alias on a\n// real ServiceNode: re-link inbound/outbound edges to the service, then drop\n// the placeholder. Returns the count of nodes promoted, for tests + logs.\n//\n// Called at the end of every extraction round. Static rounds are when new\n// aliases land (compose names, k8s metadata.name, Dockerfile labels), so\n// running it there picks up the case the issue describes: ingest fills in a\n// frontier when traffic arrives for an unknown host, and the next extraction\n// round resolves it.\n// Optional gate for block-action policies (ADR-044). When `policies` is\n// non-empty, each candidate FrontierNode runs through `canPromoteFrontier`\n// before its incident edges are rewired. Block-action policies that fire on\n// the frontier veto the promotion — the FrontierNode persists; the next\n// extract pass tries again.\nexport interface PromoteFrontierOptions {\n policies?: Policy[]\n policyCtx?: PolicyEvaluationContext\n}\n\nexport function promoteFrontierNodes(\n graph: NeatGraph,\n opts: PromoteFrontierOptions = {},\n): number {\n const aliasIndex = new Map<string, string>()\n graph.forEachNode((id, attrs) => {\n const a = attrs as ServiceNode & { type?: string }\n if (a.type !== NodeType.ServiceNode) return\n aliasIndex.set(a.name, id)\n if (a.aliases) {\n for (const alias of a.aliases) aliasIndex.set(alias, id)\n }\n })\n\n const toPromote: { frontierId: string; serviceId: string }[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as FrontierNode & { type?: string }\n if (a.type !== NodeType.FrontierNode) return\n const target = aliasIndex.get(a.host)\n if (!target) return\n if (target === id) return\n toPromote.push({ frontierId: id, serviceId: target })\n })\n\n let promoted = 0\n for (const { frontierId, serviceId } of toPromote) {\n if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {\n const gate = canPromoteFrontier(graph, frontierId, opts.policies, opts.policyCtx)\n if (!gate.allowed) {\n // Block-action policy fired on this frontier — skip the rewire and\n // leave the FrontierNode in place. Violations already surfaced via\n // the policy log on the same evaluation pass.\n continue\n }\n }\n rewireFrontierEdges(graph, frontierId, serviceId)\n graph.dropNode(frontierId)\n promoted++\n }\n return promoted\n}\n\nfunction rewireFrontierEdges(graph: NeatGraph, frontierId: string, serviceId: string): void {\n const inbound = [...graph.inboundEdges(frontierId)]\n const outbound = [...graph.outboundEdges(frontierId)]\n\n for (const edgeId of inbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n rebuildEdge(graph, edge, edge.source, serviceId, edgeId)\n }\n for (const edgeId of outbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n rebuildEdge(graph, edge, serviceId, edge.target, edgeId)\n }\n}\n\nfunction rebuildEdge(\n graph: NeatGraph,\n edge: GraphEdge,\n newSource: string,\n newTarget: string,\n oldEdgeId: string,\n): void {\n graph.dropEdge(oldEdgeId)\n // ADR-068 — promotion rewrites the target ref; provenance carries forward.\n // An OBSERVED edge to a FrontierNode promotes to an OBSERVED edge to the\n // matched typed node; an INFERRED edge stays INFERRED; etc.\n const newId =\n edge.provenance === Provenance.OBSERVED\n ? observedEdgeId(newSource, newTarget, edge.type)\n : edge.provenance === Provenance.INFERRED\n ? inferredEdgeId(newSource, newTarget, edge.type)\n : extractedEdgeId(newSource, newTarget, edge.type)\n\n if (graph.hasEdge(newId)) {\n const existing = graph.getEdgeAttributes(newId) as GraphEdge\n const merged: GraphEdge = {\n ...existing,\n callCount: (existing.callCount ?? 0) + (edge.callCount ?? 0),\n lastObserved: pickLater(existing.lastObserved, edge.lastObserved),\n }\n graph.replaceEdgeAttributes(newId, merged)\n return\n }\n\n const rebuilt: GraphEdge = {\n ...edge,\n id: newId,\n source: newSource,\n target: newTarget,\n }\n graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt)\n}\n\nfunction pickLater(a: string | undefined, b: string | undefined): string | undefined {\n if (!a) return b\n if (!b) return a\n return new Date(a).getTime() >= new Date(b).getTime() ? a : b\n}\n\nexport function makeSpanHandler(ctx: IngestContext): (span: ParsedSpan) => Promise<void> {\n return (span) => handleSpan(ctx, span)\n}\n\nexport type { StaleEvent }\n\nexport interface MarkStaleOptions {\n // Per-edge-type override map. Defaults to DEFAULT_STALE_THRESHOLDS, merged\n // with NEAT_STALE_THRESHOLDS if the env var is set.\n thresholds?: Record<string, number>\n now?: number\n // ndjson path. When set, every OBSERVED → STALE transition appends one\n // line. Skipped if undefined — tests and embedded use cases don't need a\n // log.\n staleEventsPath?: string\n // Project tag for event-bus routing (ADR-051). Defaults to DEFAULT_PROJECT.\n project?: string\n}\n\n// Demote OBSERVED edges that haven't been seen in a while. Per-edge-type\n// thresholds: HTTP CALLS go stale fast; infra DEPENDS_ON is patient. Returns\n// the count of demotions and the events appended to the log.\nexport async function markStaleEdges(\n graph: NeatGraph,\n options: MarkStaleOptions = {},\n): Promise<{ count: number; events: StaleEvent[] }> {\n const thresholds = options.thresholds ?? loadStaleThresholdsFromEnv()\n const now = options.now ?? Date.now()\n const events: StaleEvent[] = []\n\n const project = options.project ?? DEFAULT_PROJECT\n graph.forEachEdge((id, attrs) => {\n const e = attrs as GraphEdge\n if (e.provenance !== Provenance.OBSERVED) return\n if (!e.lastObserved) return\n const threshold = thresholdForEdgeType(e.type, thresholds)\n const age = now - new Date(e.lastObserved).getTime()\n if (age > threshold) {\n const updated: GraphEdge = { ...e, provenance: Provenance.STALE, confidence: 0.3 }\n graph.replaceEdgeAttributes(id, updated)\n events.push({\n edgeId: id,\n source: e.source,\n target: e.target,\n edgeType: e.type,\n thresholdMs: threshold,\n ageMs: age,\n lastObserved: e.lastObserved,\n transitionedAt: new Date(now).toISOString(),\n })\n // Stale-transition fires through the bus (ADR-051). The graph\n // subscription in events.ts can't see the OBSERVED→STALE semantic on\n // its own — a provenance flip is just an attribute update from\n // graphology's view.\n emitNeatEvent({\n type: 'stale-transition',\n project,\n payload: {\n edgeId: id,\n from: Provenance.OBSERVED,\n to: Provenance.STALE,\n },\n })\n }\n })\n\n if (options.staleEventsPath && events.length > 0) {\n await appendStaleEvents(options.staleEventsPath, events)\n }\n\n return { count: events.length, events }\n}\n\nasync function appendStaleEvents(staleEventsPath: string, events: StaleEvent[]): Promise<void> {\n await fs.mkdir(path.dirname(staleEventsPath), { recursive: true })\n const lines = events.map((e) => JSON.stringify(e)).join('\\n') + '\\n'\n await fs.appendFile(staleEventsPath, lines, 'utf8')\n}\n\nexport async function readStaleEvents(staleEventsPath: string): Promise<StaleEvent[]> {\n try {\n const raw = await fs.readFile(staleEventsPath, 'utf8')\n return raw\n .split('\\n')\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as StaleEvent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\nexport interface StalenessLoopOptions {\n thresholds?: Record<string, number>\n intervalMs?: number\n staleEventsPath?: string\n // Project tag for event-bus routing (ADR-051).\n project?: string\n // Post-stale-transition policy trigger (ADR-043). Fires after each tick of\n // markStaleEdges so policies see the new STALE state. Daemons wire this to\n // evaluateAllPolicies + PolicyViolationsLog.append.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n}\n\nexport function startStalenessLoop(\n graph: NeatGraph,\n options: StalenessLoopOptions = {},\n): () => void {\n let stopped = false\n const intervalMs = options.intervalMs ?? 60_000\n const tick = (): void => {\n if (stopped) return\n void (async () => {\n try {\n await markStaleEdges(graph, {\n thresholds: options.thresholds,\n staleEventsPath: options.staleEventsPath,\n project: options.project,\n })\n if (options.onPolicyTrigger) await options.onPolicyTrigger(graph)\n } catch (err) {\n console.error('staleness tick failed', err)\n }\n })()\n }\n const interval = setInterval(tick, intervalMs)\n if (typeof interval.unref === 'function') interval.unref()\n return () => {\n stopped = true\n clearInterval(interval)\n }\n}\n\nexport async function readErrorEvents(errorsPath: string): Promise<ErrorEvent[]> {\n try {\n const raw = await fs.readFile(errorsPath, 'utf8')\n const events = raw\n .split('\\n')\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as ErrorEvent)\n return dedupeIncidents(events)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\n// A synthesized HTTP-status incident carries no failure of its own — it's the\n// \"500 on GET /users/:id\" line handleSpan mints for a server span that answered\n// 5xx with no exception event of its own (httpFailureMessage). When the real\n// failure surfaced deeper in the same trace (a DB driver threw, a downstream\n// gRPC returned UNAVAILABLE), that exception is recorded as its own incident on\n// the same node, and the server's HTTP echo is a duplicate of it. A record is\n// \"synthesized HTTP\" when it carries no exception data, no explicit errorType\n// (the coalesced http-failure incidents set one and carry their own count), and\n// its message is exactly the HTTP line re-derived from its own attributes.\nfunction isSynthesizedHttpIncident(ev: ErrorEvent): boolean {\n if (ev.exceptionType || ev.exceptionStacktrace) return false\n if (ev.errorType) return false\n if (!ev.attributes) return false\n const synth = httpFailureMessageFromAttrs(ev.attributes)\n return synth !== undefined && synth === ev.errorMessage\n}\n\n// Make the incident surface idempotent per failure. Two passes:\n//\n// Pass 1 — collapse exact `(traceId, spanId)` re-deliveries. The ndjson sidecar\n// is append-only (persistence contract), so a re-delivered span — OTel\n// BatchSpanProcessor retries, or a receiver + handler both writing one POST —\n// leaves duplicate lines on disk. The deterministic incident `id` already\n// encodes the pair (`${traceId}:${spanId}`); we dedupe on it directly, falling\n// back to the raw pair for any record that predates the id. Records that carry\n// neither (extract parse-failure rows, `source: 'extract'`) pass through\n// untouched — they aren't span incidents. First write wins so the original\n// timestamp is preserved.\n//\n// Pass 2 — collapse one failure recorded from two spans of the same trace. A\n// failing request lands one incident from the span that actually threw (the DB\n// child's exception, a downstream gRPC error) and a second, synthesized one from\n// the HTTP server span that echoed it as a 5xx. Both key to the same\n// `(traceId, affectedNode)`; the exact-id pass can't see it because the spanIds\n// differ. When a real failure shares a trace and node with a synthesized HTTP\n// echo, drop the echo so the request counts once (issue #624). A cross-service\n// failure keeps both sides: the caller's failing-response incident and the\n// callee's exception land on different `affectedNode`s (separate ledgers per the\n// otel-ingest contract), so they never share a group.\nfunction dedupeIncidents(events: ErrorEvent[]): ErrorEvent[] {\n const seen = new Set<string>()\n const once: ErrorEvent[] = []\n for (const ev of events) {\n const key =\n ev.id ??\n (ev.traceId && ev.spanId ? `${ev.traceId}:${ev.spanId}` : undefined)\n if (key === undefined) {\n once.push(ev)\n continue\n }\n if (seen.has(key)) continue\n seen.add(key)\n once.push(ev)\n }\n\n const groupKey = (ev: ErrorEvent): string => `${ev.traceId}\\u0000${ev.affectedNode}`\n const hasRealFailure = new Set<string>()\n for (const ev of once) {\n if (ev.traceId && !isSynthesizedHttpIncident(ev)) hasRealFailure.add(groupKey(ev))\n }\n return once.filter((ev) => {\n if (!ev.traceId || !isSynthesizedHttpIncident(ev)) return true\n return !hasRealFailure.has(groupKey(ev))\n })\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Snapshot merge (ADR-074 §1)\n//\n// `neat sync` (local) and `neat sync --to <url>` (remote) feed snapshots into\n// a live graph through this helper. It lives in ingest.ts because mutation\n// authority sits with ingest + extract per the lifecycle contract (ADR-030);\n// the merge is ingestion of an external snapshot, no different in shape from\n// the way handleSpan ingests an OTel span.\n//\n// The merge preserves EXTRACTED + OBSERVED coexistence per Rule 2 — each\n// provenance variant has its own edge id, so the incoming EXTRACTED edges\n// can't stomp the daemon's accumulated OBSERVED edges and vice versa. Rule of\n// thumb: incoming wins for nodes/edges the live graph hasn't seen yet;\n// everything already present keeps its current attributes.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface MergeSnapshotResult {\n nodesAdded: number\n edgesAdded: number\n}\n\nexport function mergeSnapshot(\n graph: NeatGraph,\n snapshot: PersistedGraph,\n): MergeSnapshotResult {\n const exported = snapshot.graph as {\n nodes?: Array<{ key: string; attributes?: GraphNode }>\n edges?: Array<{ key?: string; source: string; target: string; attributes?: GraphEdge }>\n }\n\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const node of exported.nodes ?? []) {\n if (graph.hasNode(node.key)) continue\n if (!node.attributes) continue\n graph.addNode(node.key, node.attributes)\n nodesAdded++\n }\n\n for (const edge of exported.edges ?? []) {\n const attrs = edge.attributes\n if (!attrs) continue\n const id = edge.key ?? attrs.id\n if (!id) continue\n if (graph.hasEdge(id)) continue\n // Skip when either endpoint is missing — can happen if the snapshot\n // names a node the live graph already evicted and the incoming nodes\n // array didn't include.\n if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue\n graph.addEdgeWithKey(id, edge.source, edge.target, attrs)\n edgesAdded++\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type {\n ApplicablePolicy,\n GraphEdge,\n GraphNode,\n Policy,\n PolicyAction,\n PolicyFile,\n PolicyRule,\n PolicySeverity,\n PolicyViolation,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n PolicyFileSchema,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n deprecatedApis,\n nodeEngineConstraints,\n packageConflicts,\n} from './compat.js'\nimport { emitNeatEvent } from './events.js'\nimport { getBlastRadius } from './traverse.js'\n\n// Policy evaluation engine (ADR-043). The entry point evaluateAllPolicies is\n// pure: same graph + same policies → same violations. Per-rule-type dispatch\n// via the policyEvaluators table. Adding a new rule type means one new\n// evaluator entry plus the schema entry in @neat.is/types/policy.ts.\n//\n// Deterministic violation ids per ADR-043: ${policy.id}:${context}. The\n// context is shape-specific (nodeId, edgeId, or composite). The\n// policy-violations.ndjson writer skips on duplicate ids.\n\nexport interface EvaluationContext {\n // Wall-clock provider. Tests pin this; production uses Date.now.\n now: () => number\n}\n\ninterface RuleEvaluatorArgs<T extends PolicyRule = PolicyRule> {\n graph: NeatGraph\n policy: Policy\n rule: T\n ctx: EvaluationContext\n}\n\ntype RuleEvaluator<T extends PolicyRule = PolicyRule> = (\n args: RuleEvaluatorArgs<T>,\n) => PolicyViolation[]\n\n// Severity-driven default action per ADR-044.\nconst DEFAULT_ACTION_BY_SEVERITY: Record<PolicySeverity, PolicyAction> = {\n info: 'log',\n warning: 'alert',\n error: 'alert',\n critical: 'block',\n}\n\nexport function resolveOnViolation(policy: Policy): PolicyAction {\n return policy.onViolation ?? DEFAULT_ACTION_BY_SEVERITY[policy.severity]\n}\n\nfunction makeViolation(\n policy: Policy,\n rule: PolicyRule,\n contextSuffix: string,\n message: string,\n subject: PolicyViolation['subject'],\n ctx: EvaluationContext,\n): PolicyViolation {\n return {\n id: `${policy.id}:${contextSuffix}`,\n policyId: policy.id,\n policyName: policy.name,\n severity: policy.severity,\n onViolation: resolveOnViolation(policy),\n ruleType: rule.type,\n subject,\n message,\n observedAt: new Date(ctx.now()).toISOString(),\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Per-rule-type evaluators\n// ──────────────────────────────────────────────────────────────────────────\n\nconst evaluateStructural: RuleEvaluator<Extract<PolicyRule, { type: 'structural' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== rule.fromNodeType) return\n let satisfied = false\n for (const edgeId of graph.outboundEdges(id)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== rule.edgeType) continue\n const target = graph.getNodeAttributes(e.target) as GraphNode\n // FrontierNodes are unresolved peers (ADR-068) — skip; the rule\n // counts edges that resolve to a real typed node only.\n if (target.type === NodeType.FrontierNode) continue\n if (target.type === rule.toNodeType) {\n satisfied = true\n break\n }\n }\n if (!satisfied) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.fromNodeType} ${id} has no ${rule.edgeType} edge to a ${rule.toNodeType}`,\n { nodeId: id },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateOwnership: RuleEvaluator<Extract<PolicyRule, { type: 'ownership' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode & Record<string, unknown>\n if (a.type !== rule.nodeType) return\n const value = a[rule.field]\n if (typeof value !== 'string' || value.length === 0) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.nodeType} ${id} is missing required field \"${rule.field}\"`,\n { nodeId: id },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateProvenance: RuleEvaluator<Extract<PolicyRule, { type: 'provenance' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const required = Array.isArray(rule.required) ? new Set(rule.required) : new Set([rule.required])\n const violations: PolicyViolation[] = []\n graph.forEachEdge((edgeId, attrs) => {\n const e = attrs as GraphEdge\n if (e.type !== rule.edgeType) return\n if (rule.targetNodeId && e.target !== rule.targetNodeId) return\n if (!required.has(e.provenance)) {\n const requiredList = [...required].join(' | ')\n violations.push(\n makeViolation(\n policy,\n rule,\n edgeId,\n `${rule.edgeType} edge ${edgeId} has provenance ${e.provenance}; required ${requiredList}`,\n { edgeId },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateBlastRadius: RuleEvaluator<Extract<PolicyRule, { type: 'blast-radius' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n const depth = rule.depth\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== rule.nodeType) return\n const result = depth !== undefined ? getBlastRadius(graph, id, depth) : getBlastRadius(graph, id)\n if (result.totalAffected > rule.maxAffected) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.nodeType} ${id} has blast radius ${result.totalAffected} > ${rule.maxAffected}`,\n { nodeId: id, path: [id] },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateCompatibility: RuleEvaluator<Extract<PolicyRule, { type: 'compatibility' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n // Iterate every ServiceNode and re-run the compat shapes the static\n // extractor runs at extract time. Catches OBSERVED-vs-EXTRACTED divergence:\n // a service whose dep manifest changed since the last extract gets re-flagged\n // here on every evaluation cycle.\n const wantsKind = (kind: NonNullable<typeof rule.kind>): boolean =>\n rule.kind === undefined || rule.kind === kind\n\n graph.forEachNode((svcId, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== NodeType.ServiceNode) return\n const svc = a as ServiceNode\n const deps = svc.dependencies ?? {}\n\n if (wantsKind('driver-engine')) {\n // Walk every CONNECTS_TO edge from this service to a DatabaseNode,\n // then run the driver-engine compat for each (driver, declared, engine,\n // engineVersion) tuple.\n for (const edgeId of graph.outboundEdges(svcId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONNECTS_TO) continue\n const dbAttrs = graph.getNodeAttributes(e.target) as GraphNode\n // FrontierNodes are unresolved peers (ADR-068) — skip; compat\n // checking needs a typed DatabaseNode.\n if (dbAttrs.type === NodeType.FrontierNode) continue\n if (dbAttrs.type !== NodeType.DatabaseNode) continue\n const db = dbAttrs as { engine: string; engineVersion: string }\n for (const pair of compatPairs()) {\n if (pair.engine !== db.engine) continue\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(pair.driver, declared, db.engine, db.engineVersion)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:driver-engine:${pair.driver}@${declared}:${db.engine}@${db.engineVersion}`,\n result.reason,\n { nodeId: svcId, edgeId },\n ctx,\n ),\n )\n }\n }\n }\n }\n\n if (wantsKind('node-engine')) {\n const serviceNodeRange = svc.nodeEngine\n for (const constraint of nodeEngineConstraints()) {\n const declared = deps[constraint.package]\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeRange)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:node-engine:${constraint.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n\n if (wantsKind('package-conflict')) {\n for (const conflict of packageConflicts()) {\n const declared = deps[conflict.package]\n if (!declared) continue\n const requiredDeclared = deps[conflict.requires.name]\n const result = checkPackageConflict(conflict, declared, requiredDeclared)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:package-conflict:${conflict.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n\n if (wantsKind('deprecated-api')) {\n for (const dep of deprecatedApis()) {\n const declared = deps[dep.package]\n if (!declared) continue\n const result = checkDeprecatedApi(dep, declared)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:deprecated-api:${dep.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n })\n\n return violations\n}\n\nconst policyEvaluators: { [K in PolicyRule['type']]: RuleEvaluator<Extract<PolicyRule, { type: K }>> } = {\n structural: evaluateStructural,\n ownership: evaluateOwnership,\n provenance: evaluateProvenance,\n 'blast-radius': evaluateBlastRadius,\n compatibility: evaluateCompatibility,\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────────\n\n// Block-action gating for FrontierNode promotion (ADR-044 §block, MVP scope).\n// Runs the policy evaluator and returns the subset of block-action violations\n// that mention the candidate FrontierNode. Callers (ingest.ts\n// promoteFrontierNodes) check `allowed` before rewiring; when false, the\n// promotion is skipped and the violations surface through the standard\n// policy-violations.ndjson channel.\n//\n// Block scope is tightly bounded per the contract: FrontierNode promotion\n// only. Other gating points (deploy, codemod, OTel auto-create) need their\n// own ADRs before this function expands.\nexport function canPromoteFrontier(\n graph: NeatGraph,\n frontierId: string,\n policies: Policy[],\n ctx: EvaluationContext,\n): { allowed: boolean; violations: PolicyViolation[] } {\n if (policies.length === 0) return { allowed: true, violations: [] }\n const all = evaluateAllPolicies(graph, policies, ctx)\n const blocking = all.filter((v) => {\n if (v.onViolation !== 'block') return false\n return (\n v.subject.nodeId === frontierId ||\n v.subject.path?.includes(frontierId) === true\n )\n })\n return { allowed: blocking.length === 0, violations: blocking }\n}\n\nexport function evaluateAllPolicies(\n graph: NeatGraph,\n policies: Policy[],\n ctx: EvaluationContext,\n): PolicyViolation[] {\n const out: PolicyViolation[] = []\n for (const policy of policies) {\n const evaluator = policyEvaluators[policy.rule.type] as RuleEvaluator\n const violations = evaluator({ graph, policy, rule: policy.rule, ctx })\n for (const v of violations) out.push(v)\n }\n return out\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Soft guardrail — applicable-policy selection (ADR-108)\n// ──────────────────────────────────────────────────────────────────────────\n\n// The launch form of \"every agent stays inside the lines\": policies INFORM,\n// they never block. selectApplicablePolicies answers \"which policies govern the\n// node I'm about to edit?\" so the rules can ride into the agent's context.\n//\n// Matching is a subject/region match:\n// - subject — the node's type is the rule's declared subject (the rule\n// governs every node of that type).\n// - region — for most rule types, the node sits one hop inside the rule's\n// region: the target end of a structural edge, the database a compat rule\n// reaches across a CONNECTS_TO, or a node sitting on an edge a provenance\n// rule governs. For blast-radius rules the region is the subject's real\n// blast radius — its dependents: we walk getBlastRadius (to the rule's\n// declared depth) and surface the rule on every dependent it reaches, so a\n// far-away node that breaks when the subject changes still sees the\n// invariant that governs it.\n//\n// The general scope×distance injection over the policy overlay (ADR-105 §5),\n// which would carry this reachability behaviour to every rule type rather than\n// just blast-radius, is still unbuilt; the gap is written up in\n// docs/quirks/policies-soft-guardrail.md. selectApplicablePolicies never\n// evaluates a violation and never returns a verdict — surfacing a policy is not\n// gating it.\nexport function selectApplicablePolicies(\n graph: NeatGraph,\n policies: Policy[],\n nodeId: string,\n): ApplicablePolicy[] {\n // Without the node in the graph there's no type to match against, so we\n // can't decide applicability. A not-yet-created node (a brand-new edit) is\n // exactly the case the unbuilt overlay would handle; here we return empty\n // rather than guess.\n if (!graph.hasNode(nodeId)) return []\n const node = graph.getNodeAttributes(nodeId) as GraphNode\n const out: ApplicablePolicy[] = []\n for (const policy of policies) {\n const m = matchPolicyToNode(graph, policy, nodeId, node)\n if (!m) continue\n out.push({\n policyId: policy.id,\n policyName: policy.name,\n ...(policy.description !== undefined ? { description: policy.description } : {}),\n severity: policy.severity,\n onViolation: resolveOnViolation(policy),\n ruleType: policy.rule.type,\n match: m.match,\n reason: m.reason,\n })\n }\n return out\n}\n\ninterface PolicyMatch {\n match: ApplicablePolicy['match']\n reason: string\n}\n\nfunction requiredProvenanceList(required: string | readonly string[]): string {\n // required is ProvenanceRule['required'] — a single value or a non-empty\n // array. Normalize to a readable \"A | B\" string.\n if (Array.isArray(required)) return required.join(' | ')\n return String(required)\n}\n\nfunction nodeTouchesEdgeType(\n graph: NeatGraph,\n nodeId: string,\n edgeType: string,\n requiredOtherEnd?: string,\n): boolean {\n const incident = [...graph.outboundEdges(nodeId), ...graph.inboundEdges(nodeId)]\n for (const edgeId of incident) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== edgeType) continue\n // A provenance rule with a targetNodeId only governs edges that touch that\n // target. Without one, any edge of the type counts.\n if (requiredOtherEnd === undefined) return true\n if (e.source === requiredOtherEnd || e.target === requiredOtherEnd) return true\n }\n return false\n}\n\n// Reverse-reachability for blast-radius rules. Returns the id of a subject-type\n// node whose blast radius (its dependents, to the rule's declared depth)\n// contains nodeId, or null if none does. This is what lets a blast-radius rule\n// surface on the dependents it actually governs, not only on its subject.\nfunction blastRadiusSubjectReaching(\n graph: NeatGraph,\n rule: Extract<PolicyRule, { type: 'blast-radius' }>,\n nodeId: string,\n): string | null {\n let found: string | null = null\n graph.forEachNode((subjId, attrs) => {\n if (found !== null) return\n if (subjId === nodeId) return\n if ((attrs as GraphNode).type !== rule.nodeType) return\n const radius =\n rule.depth !== undefined\n ? getBlastRadius(graph, subjId, rule.depth)\n : getBlastRadius(graph, subjId)\n if (radius.affectedNodes.some((n) => n.nodeId === nodeId)) found = subjId\n })\n return found\n}\n\nfunction matchPolicyToNode(\n graph: NeatGraph,\n policy: Policy,\n nodeId: string,\n node: GraphNode,\n): PolicyMatch | null {\n const rule = policy.rule\n switch (rule.type) {\n case 'structural': {\n if (node.type === rule.fromNodeType) {\n return {\n match: 'subject',\n reason: `every ${rule.fromNodeType} must have a ${rule.edgeType} edge to a ${rule.toNodeType}`,\n }\n }\n // The target end is one hop inside the rule's region — editing it can\n // make a sibling fromNodeType pass or fail the rule.\n if (node.type === rule.toNodeType) {\n return {\n match: 'region',\n reason: `${rule.fromNodeType} nodes must reach a ${rule.toNodeType} like this one via a ${rule.edgeType} edge`,\n }\n }\n return null\n }\n case 'ownership': {\n if (node.type === rule.nodeType) {\n return {\n match: 'subject',\n reason: `every ${rule.nodeType} must declare a non-empty \"${rule.field}\" field`,\n }\n }\n return null\n }\n case 'blast-radius': {\n const depthLabel = rule.depth !== undefined ? ` at depth ${rule.depth}` : ''\n if (node.type === rule.nodeType) {\n return {\n match: 'subject',\n reason: `no ${rule.nodeType} may exceed a blast radius of ${rule.maxAffected}${depthLabel}`,\n }\n }\n // Dependent surfacing. The whole point of a blast-radius rule is \"this\n // subject has too many things depending on it\" — so the agent who most\n // needs the warning is the one editing one of those dependents, the nodes\n // that break if the subject changes. We compute the subject's actual\n // blast radius with getBlastRadius (to the rule's own declared depth) and\n // surface the rule on any dependent inside it. This is the buildable slice\n // of ADR-105 §5's scope×distance injection: a real reverse-reachability\n // walk, not the general overlay, so a far-away node still surfaces the\n // invariant.\n const subject = blastRadiusSubjectReaching(graph, rule, nodeId)\n if (subject) {\n return {\n match: 'region',\n reason: `this node is in the blast radius of ${subject} (a ${rule.nodeType} held to a blast radius of ${rule.maxAffected}${depthLabel}) — it breaks if that ${rule.nodeType} changes`,\n }\n }\n return null\n }\n case 'compatibility': {\n const kindLabel = rule.kind ?? 'all compat shapes'\n // The evaluator iterates every ServiceNode, so a ServiceNode is the\n // direct subject.\n if (node.type === NodeType.ServiceNode) {\n return {\n match: 'subject',\n reason: `this service's dependencies are compatibility-checked (${kindLabel})`,\n }\n }\n // A DatabaseNode one CONNECTS_TO hop from a service is in the region of\n // the driver-engine shape — its engine version feeds that check.\n const reachesDriverEngine = rule.kind === undefined || rule.kind === 'driver-engine'\n if (\n reachesDriverEngine &&\n node.type === NodeType.DatabaseNode &&\n nodeTouchesEdgeType(graph, nodeId, EdgeType.CONNECTS_TO)\n ) {\n return {\n match: 'region',\n reason: 'services connecting to this database have their driver/engine compatibility checked against it',\n }\n }\n return null\n }\n case 'provenance': {\n const requiredList = requiredProvenanceList(rule.required)\n // The named target is the direct subject — every governed edge points at\n // it.\n if (rule.targetNodeId !== undefined && nodeId === rule.targetNodeId) {\n return {\n match: 'subject',\n reason: `every ${rule.edgeType} edge into ${rule.targetNodeId} must carry ${requiredList} provenance`,\n }\n }\n // Otherwise the node is in the region if it sits on a governed edge.\n if (nodeTouchesEdgeType(graph, nodeId, rule.edgeType, rule.targetNodeId)) {\n return {\n match: 'region',\n reason:\n rule.targetNodeId !== undefined\n ? `this node sits on a ${rule.edgeType} edge to ${rule.targetNodeId}, which must carry ${requiredList} provenance`\n : `this node sits on a ${rule.edgeType} edge, which must carry ${requiredList} provenance`,\n }\n }\n return null\n }\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Loader\n// ──────────────────────────────────────────────────────────────────────────\n\n// Reads <projectRoot>/policy.json. Returns [] when the file doesn't exist —\n// a project without policies is a perfectly fine state. Failures to parse\n// throw with the Zod error so the daemon surfaces malformed files loudly\n// instead of silently dropping rules.\nexport async function loadPolicyFile(policyPath: string): Promise<Policy[]> {\n let raw: string\n try {\n raw = await fs.readFile(policyPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n const json = JSON.parse(raw) as unknown\n const file: PolicyFile = PolicyFileSchema.parse(json)\n return file.policies\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Append-only ndjson writer with id-based dedup\n// ──────────────────────────────────────────────────────────────────────────\n\n// Keeps an in-memory Set of seen violation ids so re-evaluation cycles don't\n// produce duplicate ndjson lines. The set hydrates from disk on first append\n// — startups that load an existing log don't lose dedup state.\nexport class PolicyViolationsLog {\n private readonly path: string\n private readonly project: string\n private seen: Set<string> | null = null\n\n constructor(logPath: string, project: string = DEFAULT_PROJECT) {\n this.path = logPath\n this.project = project\n }\n\n async append(v: PolicyViolation): Promise<boolean> {\n if (!this.seen) await this.hydrate()\n if (this.seen!.has(v.id)) return false\n this.seen!.add(v.id)\n await fs.mkdir(path.dirname(this.path), { recursive: true })\n await fs.appendFile(this.path, JSON.stringify(v) + '\\n', 'utf8')\n // Emit policy-violation only on first sighting (post-dedup) so SSE\n // consumers don't see the same violation again on every evaluation\n // cycle (ADR-051 #2).\n emitNeatEvent({\n type: 'policy-violation',\n project: this.project,\n payload: { violation: v },\n })\n return true\n }\n\n async readAll(): Promise<PolicyViolation[]> {\n try {\n const raw = await fs.readFile(this.path, 'utf8')\n return raw\n .split('\\n')\n .filter(Boolean)\n .map((line) => JSON.parse(line) as PolicyViolation)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n }\n\n private async hydrate(): Promise<void> {\n this.seen = new Set()\n const existing = await this.readAll()\n for (const v of existing) this.seen.add(v.id)\n }\n}\n","// Frontend-facing event bus (ADR-051). The single in-process EventEmitter\n// every producer (ingest / extract / watch / policy) emits through and the\n// only thing the SSE handler in streaming.ts subscribes to.\n//\n// Direct producer-to-handler coupling is a contract violation — every event\n// goes through `eventBus.emit('event', envelope)` so the SSE layer is the\n// only consumer that has to know the wire taxonomy.\n//\n// The taxonomy is locked at eight types (ADR-051 #2). Adding a ninth type\n// requires a successor ADR.\n\nimport { EventEmitter } from 'node:events'\nimport type { GraphEdge, GraphNode, PolicyViolation, Provenance } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\n// Locked event taxonomy. Eight values. Tests assert the array shape\n// directly, so adding here without updating the contract is a regression.\nexport const NEAT_EVENT_TYPES = [\n 'node-added',\n 'node-updated',\n 'node-removed',\n 'edge-added',\n 'edge-removed',\n 'extraction-complete',\n 'policy-violation',\n 'stale-transition',\n] as const\n\nexport type NeatEventType = (typeof NEAT_EVENT_TYPES)[number]\n\n// Per-type payload shapes. The wire layer (SSE) writes the payload as the\n// `data` field; producers use `emit*` helpers below for type safety.\nexport interface NodeAddedPayload {\n node: GraphNode\n}\nexport interface NodeUpdatedPayload {\n id: string\n changes: Partial<GraphNode>\n}\nexport interface NodeRemovedPayload {\n id: string\n}\nexport interface EdgeAddedPayload {\n edge: GraphEdge\n}\nexport interface EdgeRemovedPayload {\n id: string\n}\nexport interface ExtractionCompletePayload {\n project: string\n fileCount: number\n nodesAdded: number\n edgesAdded: number\n}\nexport interface PolicyViolationPayload {\n violation: PolicyViolation\n}\nexport interface StaleTransitionPayload {\n edgeId: string\n from: typeof Provenance.OBSERVED\n to: typeof Provenance.STALE\n}\n\nexport type NeatEventPayload = {\n 'node-added': NodeAddedPayload\n 'node-updated': NodeUpdatedPayload\n 'node-removed': NodeRemovedPayload\n 'edge-added': EdgeAddedPayload\n 'edge-removed': EdgeRemovedPayload\n 'extraction-complete': ExtractionCompletePayload\n 'policy-violation': PolicyViolationPayload\n 'stale-transition': StaleTransitionPayload\n}\n\n// What the bus carries internally. Project is metadata used by the SSE\n// handler to route between /events and /projects/:project/events; it never\n// lands in the wire payload.\nexport interface NeatEventEnvelope<T extends NeatEventType = NeatEventType> {\n type: T\n project: string\n payload: NeatEventPayload[T]\n}\n\n// Single event channel — listeners filter by `envelope.type` and\n// `envelope.project`. Using one channel keeps the contract surface narrow\n// and matches the SSE handler's needs (one subscription, fan out).\nexport const EVENT_BUS_CHANNEL = 'event'\n\nclass NeatEventBus extends EventEmitter {}\n\n// Singleton. Process-wide so producers in ingest / extract / watch / policy\n// can emit without threading a bus instance through every call site.\nexport const eventBus: NeatEventBus = new NeatEventBus()\n\n// EventEmitter defaults to 10 listeners; SSE clients add up quickly under a\n// browser refresh storm, so lift the cap.\neventBus.setMaxListeners(0)\n\nexport function emitNeatEvent<T extends NeatEventType>(envelope: NeatEventEnvelope<T>): void {\n eventBus.emit(EVENT_BUS_CHANNEL, envelope)\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Graph subscription — one place to wire graphology mutation events into\n// the bus so producers don't have to instrument every addNode/addEdge.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface AttachOptions {\n project: string\n}\n\n// Subscribes to a NeatGraph and re-emits node/edge add/remove + node/edge\n// attribute updates as bus envelopes scoped to `project`. Returns a detach\n// fn that removes every listener it installed.\n//\n// Stale-transition is NOT routed through here — a provenance flip is just an\n// attribute update from graphology's view, and we'd lose the OBSERVED→STALE\n// semantic. ingest.ts emits stale-transition itself.\nexport function attachGraphToEventBus(graph: NeatGraph, opts: AttachOptions): () => void {\n const { project } = opts\n\n const onNodeAdded = (payload: { key: string; attributes: GraphNode }): void => {\n emitNeatEvent({\n type: 'node-added',\n project,\n payload: { node: payload.attributes },\n })\n }\n const onNodeDropped = (payload: { key: string }): void => {\n emitNeatEvent({\n type: 'node-removed',\n project,\n payload: { id: payload.key },\n })\n }\n const onEdgeAdded = (payload: { key: string; attributes: GraphEdge }): void => {\n emitNeatEvent({\n type: 'edge-added',\n project,\n payload: { edge: payload.attributes },\n })\n }\n const onEdgeDropped = (payload: { key: string }): void => {\n emitNeatEvent({\n type: 'edge-removed',\n project,\n payload: { id: payload.key },\n })\n }\n const onNodeAttrsUpdated = (payload: { key: string; attributes: GraphNode }): void => {\n emitNeatEvent({\n type: 'node-updated',\n project,\n payload: { id: payload.key, changes: payload.attributes },\n })\n }\n\n graph.on('nodeAdded', onNodeAdded)\n graph.on('nodeDropped', onNodeDropped)\n graph.on('edgeAdded', onEdgeAdded)\n graph.on('edgeDropped', onEdgeDropped)\n graph.on('nodeAttributesUpdated', onNodeAttrsUpdated)\n\n return () => {\n graph.off('nodeAdded', onNodeAdded)\n graph.off('nodeDropped', onNodeDropped)\n graph.off('edgeAdded', onEdgeAdded)\n graph.off('edgeDropped', onEdgeDropped)\n graph.off('nodeAttributesUpdated', onNodeAttrsUpdated)\n }\n}\n","// ADR-065 — loud failure mode for static extraction.\n//\n// Per-file extraction failures used to land as a single `console.warn` line\n// with no aggregate count and no on-disk record. The 2026-05-12 medusa\n// experiment ran `neat init` against ~90 files that all silently failed\n// extraction with \"Invalid argument\"; the snapshot shipped with those files\n// missing, the user had no signal it had happened, and the divergence query\n// couldn't surface gaps it didn't know it had.\n//\n// This module is the failure-mode plumbing: a process-local sink that every\n// producer appends to when a per-file parse fails, with helpers to drain the\n// sink to `<projectDir>/neat-out/errors.ndjson` and to surface the aggregate\n// count in init / watch banners.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport interface ExtractionError {\n // Producer name (e.g. \"http\", \"services\", \"infra docker-compose\"). Stable\n // human-readable identifier for logs and contract assertions.\n producer: string\n // Absolute or relative path of the file that failed. Stored verbatim from\n // the call site; no normalisation here.\n file: string\n // The error's `.message`. Stringified if the throw value wasn't an Error.\n error: string\n // Optional stack trace (captured at the call site). Useful for diagnosing\n // tree-sitter \"Invalid argument\" cases where the message alone is generic.\n stack?: string\n // ISO timestamp of when the error was recorded.\n ts: string\n // Discriminator for `errors.ndjson` consumers — separates extract failures\n // from OTel error events that share the same file (ADR-033).\n source: 'extract'\n}\n\nconst sink: ExtractionError[] = []\n\n// Record a per-file extraction failure. Preserves the visible warn line so\n// existing scripts/CI consumers still see a per-file message; appends to the\n// process-local sink so the aggregate count + sidecar write can fire later.\nexport function recordExtractionError(\n producer: string,\n file: string,\n err: unknown,\n): void {\n const e = err instanceof Error ? err : new Error(String(err))\n sink.push({\n producer,\n file,\n error: e.message,\n stack: e.stack,\n ts: new Date().toISOString(),\n source: 'extract',\n })\n // Visible warn preserved (pre-ADR-065 callers logged this). Banners\n // aggregate, but per-file context still useful for tail -f sessions.\n console.warn(`[neat] ${producer} skipped ${file}: ${e.message}`)\n}\n\n// Drain all queued errors. Idempotent — subsequent calls return an empty\n// array until new errors land. Callers (extractFromDirectory) drain at start\n// to clear stale state from prior runs, then drain again at end to collect\n// the pass's failures.\nexport function drainExtractionErrors(): ExtractionError[] {\n return sink.splice(0, sink.length)\n}\n\n// Read-only count of currently-queued errors. Used by callers that want the\n// count without consuming the sink (the banner case — we want the count for\n// display and the entries for `errors.ndjson`).\nexport function pendingExtractionErrors(): number {\n return sink.length\n}\n\n// Append the drained entries to `<projectDir>/neat-out/errors.ndjson`. The\n// file is shared with OTel error events (per ADR-033); the `source: 'extract'`\n// discriminator separates them for consumers. Creates the directory if\n// missing. Append-only — never rewritten.\nexport async function writeExtractionErrors(\n errors: ExtractionError[],\n errorsPath: string,\n): Promise<void> {\n if (errors.length === 0) return\n await fs.mkdir(path.dirname(errorsPath), { recursive: true })\n const lines = errors.map((e) => JSON.stringify(e)).join('\\n') + '\\n'\n await fs.appendFile(errorsPath, lines, 'utf8')\n}\n\n// ADR-065 — `NEAT_STRICT_EXTRACTION=1` makes any per-file extraction failure\n// cause the calling command to exit non-zero. Default is forgiving (banner\n// only). The check is at the caller (cli.ts / server.ts / watch.ts) so the\n// extraction phase itself stays library-shaped.\nexport function isStrictExtractionEnabled(): boolean {\n const raw = process.env.NEAT_STRICT_EXTRACTION\n return raw === '1' || raw === 'true'\n}\n\n// Format the unconditional summary banner. Zero errors is a positive signal\n// (\"0 files skipped\") so callers print this even on clean runs.\nexport function formatExtractionBanner(count: number): string {\n if (count === 1) return `[neat] 1 file skipped due to parse errors`\n return `[neat] ${count} files skipped due to parse errors`\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// ADR-066 — precision-floor drop accounting.\n//\n// EXTRACTED candidates whose graded confidence falls below\n// NEAT_EXTRACTED_PRECISION_FLOOR (default 0.7) are computed but never added\n// to the graph. The drop count surfaces on the extraction banner;\n// NEAT_EXTRACTED_REJECTED_LOG=1 routes the per-candidate detail to\n// `<projectDir>/neat-out/rejected.ndjson`. The default keeps the sidecar\n// surface quiet.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface DroppedExtractedEdge {\n source: string\n target: string\n type: string\n confidence: number\n confidenceKind: string\n evidence: { file: string; line?: number; snippet?: string }\n}\n\nconst droppedSink: DroppedExtractedEdge[] = []\n\nexport function noteExtractedDropped(edge: DroppedExtractedEdge): void {\n droppedSink.push(edge)\n}\n\nexport function drainDroppedExtracted(): DroppedExtractedEdge[] {\n return droppedSink.splice(0, droppedSink.length)\n}\n\nexport function pendingDroppedExtracted(): number {\n return droppedSink.length\n}\n\nexport function isRejectedLogEnabled(): boolean {\n const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG\n return raw === '1' || raw === 'true'\n}\n\nexport async function writeRejectedExtracted(\n drops: DroppedExtractedEdge[],\n rejectedPath: string,\n): Promise<void> {\n if (drops.length === 0) return\n await fs.mkdir(path.dirname(rejectedPath), { recursive: true })\n const lines = drops.map((d) => JSON.stringify({ ...d, ts: new Date().toISOString() })).join('\\n') + '\\n'\n await fs.appendFile(rejectedPath, lines, 'utf8')\n}\n\nexport function formatPrecisionFloorBanner(count: number): string {\n if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`\n return `[neat] ${count} extracted edges dropped below precision floor`\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport ignore, { type Ignore } from 'ignore'\nimport { minimatch } from 'minimatch'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType, serviceId } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport {\n IGNORED_DIRS,\n exists,\n isPythonVenvDir,\n readJson,\n type DiscoveredService,\n type PackageJson,\n} from './shared.js'\nimport { discoverPythonService, pythonToPackage } from './python.js'\nimport { computeServiceOwner, loadCodeowners } from './owners.js'\nimport { recordExtractionError } from './errors.js'\n\nconst DEFAULT_SCAN_DEPTH = 5\n\ninterface RootPackageJson extends PackageJson {\n workspaces?: string[] | { packages?: string[] }\n}\n\nfunction parseScanDepth(): number {\n const raw = process.env.NEAT_SCAN_DEPTH\n if (!raw) return DEFAULT_SCAN_DEPTH\n const n = Number.parseInt(raw, 10)\n return Number.isFinite(n) && n >= 0 ? n : DEFAULT_SCAN_DEPTH\n}\n\nfunction workspaceGlobs(pkg: RootPackageJson): string[] | null {\n const ws = pkg.workspaces\n if (!ws) return null\n if (Array.isArray(ws)) return ws.length > 0 ? ws : null\n if (Array.isArray(ws.packages)) return ws.packages.length > 0 ? ws.packages : null\n return null\n}\n\nasync function hasPythonManifest(dir: string): Promise<boolean> {\n return (\n (await exists(path.join(dir, 'pyproject.toml'))) ||\n (await exists(path.join(dir, 'requirements.txt'))) ||\n (await exists(path.join(dir, 'setup.py')))\n )\n}\n\nasync function loadGitignore(scanPath: string): Promise<Ignore | null> {\n const gitignorePath = path.join(scanPath, '.gitignore')\n if (!(await exists(gitignorePath))) return null\n const raw = await fs.readFile(gitignorePath, 'utf8')\n return ignore().add(raw)\n}\n\ninterface WalkOptions {\n maxDepth: number\n ig: Ignore | null\n}\n\nasync function walkDirs(\n start: string,\n scanPath: string,\n options: WalkOptions,\n visit: (dir: string) => Promise<void> | void,\n): Promise<void> {\n async function recurse(current: string, depth: number): Promise<void> {\n if (depth > options.maxDepth) return\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(current, entry.name)\n if (options.ig) {\n const rel = path.relative(scanPath, child).split(path.sep).join('/')\n // Trailing slash so `ignore` evaluates the entry as a directory; without\n // it, gitignore patterns like `dist/` won't match because the lib\n // distinguishes file vs. directory tests.\n if (rel && options.ig.ignores(rel + '/')) continue\n }\n // Issue #344 — `pyvenv.cfg` marks every directory that `python -m venv`\n // or `virtualenv` creates, regardless of what the wrapper dir is called.\n // The `IGNORED_DIRS` name list catches the common shapes (`.venv`,\n // `venv`, `.tox`); this catches the long-tail ones (`env-3.11`,\n // `.direnv`, ad-hoc names).\n if (await isPythonVenvDir(child)) continue\n await visit(child)\n await recurse(child, depth + 1)\n }\n }\n await recurse(start, 0)\n}\n\nasync function expandWorkspaceGlobs(\n scanPath: string,\n globs: string[],\n): Promise<string[]> {\n const found = new Set<string>()\n const scanDepth = parseScanDepth()\n\n for (const raw of globs) {\n const pattern = raw.replace(/^\\.\\//, '')\n\n if (!pattern.includes('*')) {\n const candidate = path.join(scanPath, pattern)\n if (await exists(path.join(candidate, 'package.json'))) found.add(candidate)\n continue\n }\n\n const segments = pattern.split('/')\n const staticSegments: string[] = []\n for (const seg of segments) {\n if (seg.includes('*')) break\n staticSegments.push(seg)\n }\n const start = path.join(scanPath, ...staticSegments)\n if (!(await exists(start))) continue\n\n const hasDoubleStar = pattern.includes('**')\n const walkDepth = hasDoubleStar\n ? scanDepth\n : Math.max(0, segments.length - staticSegments.length - 1)\n\n await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {\n const rel = path.relative(scanPath, dir).split(path.sep).join('/')\n if (minimatch(rel, pattern) && (await exists(path.join(dir, 'package.json')))) {\n found.add(dir)\n }\n })\n }\n\n return [...found]\n}\n\n// Framework detection from package.json deps (ADR-074 §3). Mirrors the\n// installer dispatch precedence so a project the installer recognises as\n// Remix records `framework: 'remix'` on its ServiceNode. The static\n// extractor sees only manifest data, so detection is dep-presence based —\n// it doesn't crack open config files. Detection precedence: Next → Remix\n// → SvelteKit → Nuxt → Astro → vanilla Node.\nfunction detectJsFramework(pkg: PackageJson): string | undefined {\n const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n if (deps['next'] !== undefined) return 'next'\n if (deps['remix'] !== undefined) return 'remix'\n for (const k of Object.keys(deps)) {\n if (k.startsWith('@remix-run/')) return 'remix'\n }\n if (deps['@sveltejs/kit'] !== undefined) return 'sveltekit'\n if (deps['nuxt'] !== undefined) return 'nuxt'\n if (deps['astro'] !== undefined) return 'astro'\n return undefined\n}\n\n// A JS-manifest service is TypeScript when its package depends on `typescript`\n// or carries a tsconfig — otherwise we default to javascript. The per-file\n// extractor already routes by extension (`languageForPath`); this only sets the\n// ServiceNode's own `language` field, which the static walker can't read off a\n// single file. One readdir, conservative on the javascript side.\nasync function detectJsServiceLanguage(\n dir: string,\n pkg: PackageJson,\n): Promise<'typescript' | 'javascript'> {\n const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n if (deps['typescript'] !== undefined) return 'typescript'\n const entries = await fs.readdir(dir).catch(() => [] as string[])\n if (entries.some((name) => /^tsconfig(\\..+)?\\.json$/.test(name))) return 'typescript'\n return 'javascript'\n}\n\nasync function discoverNodeService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n const pkgPath = path.join(dir, 'package.json')\n if (!(await exists(pkgPath))) return null\n let pkg: PackageJson\n try {\n pkg = await readJson<PackageJson>(pkgPath)\n } catch (err) {\n recordExtractionError('services', path.relative(scanPath, pkgPath), err)\n return null\n }\n if (!pkg.name) return null\n const framework = detectJsFramework(pkg)\n const language = await detectJsServiceLanguage(dir, pkg)\n const node: ServiceNode = {\n id: serviceId(pkg.name),\n type: NodeType.ServiceNode,\n name: pkg.name,\n language,\n version: pkg.version,\n dependencies: pkg.dependencies ?? {},\n repoPath: path.relative(scanPath, dir),\n ...(pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}),\n ...(framework ? { framework } : {}),\n }\n return { pkg, dir, node }\n}\n\nasync function discoverPyService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n const py = await discoverPythonService(dir)\n if (!py) return null\n const pkg = pythonToPackage(py)\n const node: ServiceNode = {\n id: serviceId(py.name),\n type: NodeType.ServiceNode,\n name: py.name,\n language: 'python',\n version: py.version,\n dependencies: py.dependencies,\n repoPath: path.relative(scanPath, dir),\n }\n return { pkg, dir, node }\n}\n\n// Phase 1 — discover service directories under scanPath. A service is any\n// directory containing a JS/TS manifest (`package.json`) or a Python manifest\n// (`pyproject.toml` / `requirements.txt` / `setup.py`). JS wins on tie.\n//\n// If the root `package.json` declares `workspaces`, those globs are\n// authoritative — we don't fall back to a free recursive walk. Otherwise we\n// walk recursively, depth-bounded by `NEAT_SCAN_DEPTH` (default 5), skipping\n// `IGNORED_DIRS` and anything matched by the root `.gitignore`.\n//\n// Two manifests sharing a `name` collapse to one node per ADR-010; the\n// duplicate logs a warning naming both paths.\nexport async function discoverServices(scanPath: string): Promise<DiscoveredService[]> {\n const rootPkgPath = path.join(scanPath, 'package.json')\n let rootPkg: RootPackageJson | null = null\n if (await exists(rootPkgPath)) {\n try {\n rootPkg = await readJson<RootPackageJson>(rootPkgPath)\n } catch (err) {\n recordExtractionError(\n 'services workspaces',\n path.relative(scanPath, rootPkgPath),\n err,\n )\n }\n }\n const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null\n\n const candidateDirs: string[] = []\n if (wsGlobs) {\n candidateDirs.push(...(await expandWorkspaceGlobs(scanPath, wsGlobs)))\n } else {\n if (rootPkg && rootPkg.name) {\n candidateDirs.push(scanPath)\n } else if (await hasPythonManifest(scanPath)) {\n // A Python project commonly keeps its manifest at the repo root with the\n // code in a subpackage and no package.json anywhere. The walk only visits\n // descendants, so without this the root manifest is never seen and the\n // whole project discovers zero services.\n candidateDirs.push(scanPath)\n }\n const ig = await loadGitignore(scanPath)\n await walkDirs(\n scanPath,\n scanPath,\n { maxDepth: parseScanDepth(), ig },\n async (dir) => {\n if (await exists(path.join(dir, 'package.json'))) {\n candidateDirs.push(dir)\n } else if (await hasPythonManifest(dir)) {\n candidateDirs.push(dir)\n }\n },\n )\n }\n\n candidateDirs.sort()\n\n const seen = new Map<string, string>()\n const out: DiscoveredService[] = []\n for (const dir of candidateDirs) {\n const service =\n (await discoverNodeService(scanPath, dir)) ??\n (await discoverPyService(scanPath, dir))\n if (!service) continue\n\n const existingDir = seen.get(service.node.name)\n if (existingDir !== undefined) {\n const a = path.relative(scanPath, existingDir) || '.'\n const b = path.relative(scanPath, dir) || '.'\n console.warn(\n `[neat] duplicate package name \"${service.node.name}\" — keeping ${a}, ignoring ${b}`,\n )\n continue\n }\n seen.set(service.node.name, dir)\n out.push(service)\n }\n\n // Owner extraction (ADR-054). CODEOWNERS first, package.json `author`\n // fallback, undefined otherwise. Read once per discovery pass; the file\n // is small and parsing it per-service would be wasteful.\n const codeowners = await loadCodeowners(scanPath)\n for (const service of out) {\n const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir)\n if (owner !== undefined) service.node.owner = owner\n }\n\n return out\n}\n\nexport function addServiceNodes(graph: NeatGraph, services: DiscoveredService[]): number {\n let nodesAdded = 0\n for (const service of services) {\n if (!graph.hasNode(service.node.id)) {\n graph.addNode(service.node.id, { ...service.node, discoveredVia: 'static' })\n nodesAdded++\n continue\n }\n // OTel ingest may have auto-created a minimal node at this id. Merge per\n // ADR-033 / identity contract: static fields override OTel-derived fields,\n // and discoveredVia flips to 'merged' when both layers contributed.\n const existing = graph.getNodeAttributes(service.node.id) as ServiceNode\n const mergedDiscoveredVia: 'static' | 'otel' | 'merged' =\n existing.discoveredVia === 'otel' ? 'merged' : 'static'\n graph.replaceNodeAttributes(service.node.id, {\n ...existing,\n ...service.node,\n discoveredVia: mergedDiscoveredVia,\n })\n }\n return nodesAdded\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseYaml } from 'yaml'\nimport type { ServiceNode } from '@neat.is/types'\n\nexport interface PackageJson {\n name: string\n version?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n engines?: { node?: string }\n}\n\nexport interface DiscoveredService {\n pkg: PackageJson\n dir: string\n node: ServiceNode\n}\n\nexport const SERVICE_FILE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.py'])\nexport const CONFIG_FILE_EXTENSIONS = new Set(['.yaml', '.yml'])\nexport const IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n '.turbo',\n 'dist',\n 'build',\n '.next',\n // Python virtualenv shapes (issue #344). Walking into a venv pulls in the\n // entire CPython stdlib + every installed package as if it were first-party\n // service code — 20k+ files, none of which the user wrote. The shape names\n // cover the three common venv tools (`venv` / `python -m venv`,\n // `virtualenv`) and the tox + PEP 582 conventions.\n '.venv',\n 'venv',\n '__pypackages__',\n '.tox',\n // `site-packages` shows up nested inside venvs that don't carry one of the\n // outer names (system Python on macOS, in-place pyenv layouts). Listing it\n // here means we stop the walk at the boundary even when the wrapper dir\n // wasn't recognisable.\n 'site-packages',\n])\n\n// Marker file `python -m venv` and `virtualenv` both drop at the venv root.\n// Used by the walkers to recognise venvs whose top-level directory doesn't\n// happen to match one of the canonical `IGNORED_DIRS` names (issue #344).\n// Cached per process to keep the recursion hot path off the fs after the\n// first walk; venv contents don't change inside one extract pass.\nconst PYVENV_MARKER_CACHE = new Map<string, boolean>()\n\nexport async function isPythonVenvDir(dir: string): Promise<boolean> {\n const cached = PYVENV_MARKER_CACHE.get(dir)\n if (cached !== undefined) return cached\n try {\n const stat = await fs.stat(path.join(dir, 'pyvenv.cfg'))\n const ok = stat.isFile()\n PYVENV_MARKER_CACHE.set(dir, ok)\n return ok\n } catch {\n PYVENV_MARKER_CACHE.set(dir, false)\n return false\n }\n}\n\nexport function isConfigFile(name: string): { match: boolean; fileType: string } {\n const ext = path.extname(name)\n if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) }\n // .env, .env.local, .env.production. Bare filename or any dotted-suffix\n // variant; folder names get filtered upstream by walking files only.\n // ADR-065 #4 filters .env.template / .env.example / .env.sample (and the\n // dotted-suffix variants) at the producer level — those are documentation,\n // not runtime config.\n if (name === '.env' || name.startsWith('.env.')) {\n if (isEnvTemplateFile(name)) return { match: false, fileType: '' }\n // NEAT writes `.env.neat` itself during `--apply` (ADR-069 §4). Ingesting\n // it would record our own instrumentation env as if it were the user's\n // declared config — self-pollution. Skip it the same way the template\n // filter skips onboarding docs.\n if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: '' }\n return { match: true, fileType: 'env' }\n }\n return { match: false, fileType: '' }\n}\n\n// NEAT-authored artifacts the installer drops into the user's tree during\n// `--apply` (ADR-069). Extraction skips them: they're our own runtime hooks,\n// not first-party config or source. Ingesting them pollutes the graph with\n// NEAT's instrumentation as though the user had written it.\n//\n// `.env.neat` — per-package env carrying `OTEL_SERVICE_NAME`.\n// `otel-init.{ext}` — generated SDK bootstrap (js/cjs/mjs/ts), including\n// the framework variants (`src/otel-init.*`,\n// `server/plugins/otel-init.*`) — basename is stable.\nexport function isNeatAuthoredEnvFile(name: string): boolean {\n return name === '.env.neat'\n}\n\nexport function isNeatAuthoredSourceFile(name: string): boolean {\n return /^otel-init\\.(?:js|cjs|mjs|ts|tsx)$/i.test(name)\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// ADR-065 precision-filter helpers. Pre-emit gates inside the producer pass.\n// Filtered candidates are never written to the graph (idempotency intact).\n// ─────────────────────────────────────────────────────────────────────────\n\n// ADR-065 #1 — test-scope exclusion. Returns true when the file path matches\n// any test-scope pattern. Path is normalised to forward slashes before\n// matching so callers can pass either form.\n//\n// Patterns:\n// - any segment named __tests__, __fixtures__, or integration-tests\n// - basename matches *.spec.{ts,tsx,js,jsx,mjs,cjs,py}\n// - basename matches *.test.{ts,tsx,js,jsx,mjs,cjs,py}\nexport function isTestPath(filePath: string): boolean {\n const normalised = filePath.replace(/\\\\/g, '/')\n const segments = normalised.split('/')\n for (const seg of segments) {\n if (seg === '__tests__' || seg === '__fixtures__' || seg === 'integration-tests') {\n return true\n }\n }\n const base = segments[segments.length - 1] ?? ''\n return /\\.(spec|test)\\.(?:tsx?|jsx?|mjs|cjs|py)$/i.test(base)\n}\n\n// ADR-065 #4 — `.env.template` exclusion. Matches:\n// .env.template / .env.example / .env.sample\n// .env.*.template / .env.*.example / .env.*.sample\n// These are docs/onboarding artifacts, not runtime config. ConfigNodes are\n// bound to runtime existence (ADR-016); templates fail that test.\nexport function isEnvTemplateFile(name: string): boolean {\n if (\n name === '.env.template' ||\n name === '.env.example' ||\n name === '.env.sample'\n ) {\n return true\n }\n // `.env.*.template` / `.env.*.example` / `.env.*.sample`\n return /^\\.env\\.[^.]+\\.(?:template|example|sample)$/i.test(name)\n}\n\n// ADR-065 #2 — comment-body exclusion. Replaces every JS/TS comment span in\n// the source with an equal-length run of spaces, preserving line/column for\n// downstream line-mapping. Strings that contain `//` sequences (URLs) are\n// preserved by tracking the string context as we scan.\n//\n// Not a full parser — good enough for the medusa-shape failures. The HTTP\n// extractor's AST walk already gets comment-awareness for free; this helper\n// is for the regex-based extractors (redis, kafka, aws, grpc).\nexport function maskCommentsInSource(src: string): string {\n const len = src.length\n const out: string[] = new Array(len)\n let i = 0\n // String context: ' \" ` (template) — open-quote char, 0 when not in a string.\n let inString: string | 0 = 0\n let escaped = false\n while (i < len) {\n const c = src[i]!\n if (inString !== 0) {\n out[i] = c\n if (escaped) {\n escaped = false\n } else if (c === '\\\\') {\n escaped = true\n } else if (c === inString) {\n inString = 0\n }\n i++\n continue\n }\n if (c === '/' && i + 1 < len) {\n const next = src[i + 1]!\n if (next === '/') {\n out[i] = ' '\n out[i + 1] = ' '\n let j = i + 2\n while (j < len && src[j] !== '\\n') {\n out[j] = ' '\n j++\n }\n i = j\n continue\n }\n if (next === '*') {\n out[i] = ' '\n out[i + 1] = ' '\n let j = i + 2\n while (j < len) {\n if (src[j] === '\\n') {\n out[j] = '\\n'\n j++\n continue\n }\n if (src[j] === '*' && j + 1 < len && src[j + 1] === '/') {\n out[j] = ' '\n out[j + 1] = ' '\n j += 2\n break\n }\n out[j] = ' '\n j++\n }\n i = j\n continue\n }\n }\n out[i] = c\n if (c === \"'\" || c === '\"' || c === '`') inString = c\n i++\n }\n return out.join('')\n}\n\n// ADR-065 #5 — exact hostname match for cross-service URL inference. Returns\n// true if `urlString` looks like an actual URL — has an explicit `scheme://`\n// or starts with a scheme-relative `//` — and its hostname matches `host`\n// exactly (case-insensitive). No `.includes()` containment, and no bare-string\n// matching: a literal like `'admin-bundler'` would otherwise parse as\n// `http://admin-bundler` and match the basename of every service directory,\n// which is how the v0.3.3 medusa pre-check produced 279 false positives.\n//\n// Accepts a `host` that may include a port (`api.example.com:8080`); in that\n// case the URL's hostname AND port must both match.\nconst URL_LIKE = /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i\n\nexport function urlMatchesHost(urlString: string, host: string): boolean {\n if (typeof urlString !== 'string' || urlString.length === 0) return false\n // Require the literal to look like a URL — scheme + `://` or scheme-relative\n // `//host`. Bare hostnames are rejected; they're the load-bearing source of\n // false positives.\n if (!URL_LIKE.test(urlString)) return false\n const [wantedHost, wantedPort] = host.split(':')\n let parsed: URL\n try {\n // For scheme-relative `//host/path`, prepend `http:` so URL accepts it.\n const candidate = urlString.startsWith('//') ? `http:${urlString}` : urlString\n parsed = new URL(candidate)\n } catch {\n return false\n }\n if (parsed.hostname.toLowerCase() !== (wantedHost ?? '').toLowerCase()) return false\n if (wantedPort && parsed.port !== wantedPort) return false\n return true\n}\n\n// Strip semver range prefixes (^, ~, >=, etc.) and bare \"v\" so the extracted\n// version is usable for compat checks. We don't try to resolve ranges to actual\n// installed versions — that's a published-lockfile concern, not extraction's job.\nexport function cleanVersion(raw: string | undefined): string | undefined {\n if (!raw) return undefined\n return raw.replace(/^[\\^~><=v\\s]+/, '').trim() || undefined\n}\n\nexport async function readJson<T>(filePath: string): Promise<T> {\n const raw = await fs.readFile(filePath, 'utf8')\n return JSON.parse(raw) as T\n}\n\nexport async function readYaml<T>(filePath: string): Promise<T> {\n const raw = await fs.readFile(filePath, 'utf8')\n return parseYaml(raw) as T\n}\n\nexport async function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// Thin re-export so existing callers (calls/, configs.ts, databases/, infra/)\n// keep their import path. Wire format lives in @neat.is/types/identity.ts per\n// ADR-029.\nexport { extractedEdgeId as makeEdgeId } from '@neat.is/types'\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\nimport { exists, type PackageJson } from './shared.js'\n\n// Lines like `psycopg2==2.7.0`, `psycopg2 == 2.7.0`, `psycopg2[extras]==2.7`,\n// or `psycopg2~=2.7,<3`. We capture the package name and the first version\n// that follows an `==` operator. Anything else (range, no pin, no version) is\n// recorded with an empty version — the compat matrix's semver coercer treats\n// those as \"can't reason\" and under-flags rather than over-flags.\nconst REQUIREMENT_LINE = /^\\s*([A-Za-z0-9_.-]+)(?:\\[[^\\]]*\\])?\\s*(?:(==)\\s*([A-Za-z0-9_.+-]+))?/\n\nfunction parseRequirementsTxt(content: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const rawLine of content.split('\\n')) {\n const line = rawLine.split('#')[0]?.trim()\n if (!line) continue\n if (line.startsWith('-')) continue // -r requirements-dev.txt etc.\n const match = REQUIREMENT_LINE.exec(line)\n if (!match) continue\n const name = match[1]!.toLowerCase()\n const version = match[3] ?? ''\n out[name] = version\n }\n return out\n}\n\ninterface PyProjectFile {\n project?: {\n name?: string\n version?: string\n dependencies?: string[]\n }\n tool?: {\n poetry?: {\n name?: string\n version?: string\n dependencies?: Record<string, string | { version?: string }>\n }\n }\n}\n\nfunction depsFromPyProject(pyproject: PyProjectFile): Record<string, string> {\n const out: Record<string, string> = {}\n\n // PEP 621 — [project] dependencies = [\"psycopg2==2.7.0\", \"requests\"]\n for (const entry of pyproject.project?.dependencies ?? []) {\n const match = REQUIREMENT_LINE.exec(entry)\n if (!match) continue\n out[match[1]!.toLowerCase()] = match[3] ?? ''\n }\n\n // Poetry — [tool.poetry.dependencies] psycopg2 = \"2.7.0\"\n const poetryDeps = pyproject.tool?.poetry?.dependencies ?? {}\n for (const [name, value] of Object.entries(poetryDeps)) {\n if (name.toLowerCase() === 'python') continue\n const raw = typeof value === 'string' ? value : (value?.version ?? '')\n out[name.toLowerCase()] = raw.replace(/^[\\^~><=v\\s]+/, '')\n }\n return out\n}\n\nexport interface PythonService {\n name: string\n version?: string\n dependencies: Record<string, string>\n}\n\n// Detect a Python service by the conventional manifest files. We try\n// pyproject.toml first because it can name the package; fallback to the\n// directory name when only requirements.txt or setup.py is present.\nexport async function discoverPythonService(serviceDir: string): Promise<PythonService | null> {\n const pyprojectPath = path.join(serviceDir, 'pyproject.toml')\n const requirementsPath = path.join(serviceDir, 'requirements.txt')\n const setupPath = path.join(serviceDir, 'setup.py')\n\n const hasPyproject = await exists(pyprojectPath)\n const hasRequirements = await exists(requirementsPath)\n const hasSetup = await exists(setupPath)\n if (!hasPyproject && !hasRequirements && !hasSetup) return null\n\n let name = path.basename(serviceDir)\n let version: string | undefined\n const dependencies: Record<string, string> = {}\n\n if (hasPyproject) {\n const raw = await fs.readFile(pyprojectPath, 'utf8')\n const pyproject = parseToml(raw) as PyProjectFile\n name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name\n version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? undefined\n Object.assign(dependencies, depsFromPyProject(pyproject))\n }\n\n if (hasRequirements) {\n const raw = await fs.readFile(requirementsPath, 'utf8')\n Object.assign(dependencies, parseRequirementsTxt(raw))\n }\n\n return { name, version, dependencies }\n}\n\n// Build the same `pkg`-shaped shim the JS path uses so downstream phases\n// (databases, calls, etc.) can keep reading service.pkg.dependencies and\n// service.pkg.name without caring which language produced the service.\nexport function pythonToPackage(service: PythonService): PackageJson {\n return {\n name: service.name,\n version: service.version,\n dependencies: service.dependencies,\n }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { minimatch } from 'minimatch'\nimport { exists, readJson } from './shared.js'\n\nexport interface CodeownersRule {\n pattern: string\n owners: string\n}\n\nexport interface CodeownersFile {\n rules: CodeownersRule[]\n}\n\ninterface PackageJsonAuthor {\n author?: string | { name?: string }\n}\n\n// Read CODEOWNERS at <scanPath>/CODEOWNERS first, then <scanPath>/.github/CODEOWNERS.\n// Returns null when neither exists. ADR-054 #2.1.\nexport async function loadCodeowners(scanPath: string): Promise<CodeownersFile | null> {\n const candidates = [\n path.join(scanPath, 'CODEOWNERS'),\n path.join(scanPath, '.github', 'CODEOWNERS'),\n ]\n for (const file of candidates) {\n if (await exists(file)) {\n const raw = await fs.readFile(file, 'utf8')\n return parseCodeowners(raw)\n }\n }\n return null\n}\n\nfunction parseCodeowners(raw: string): CodeownersFile {\n const rules: CodeownersRule[] = []\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) continue\n const match = /^(\\S+)\\s+(.+)$/.exec(trimmed)\n if (!match) continue\n rules.push({ pattern: match[1]!, owners: match[2]!.trim() })\n }\n return { rules }\n}\n\n// First matching pattern wins; returns the literal RHS or null. ADR-054 #2.1.\n// Pattern matcher is minimal per ADR-054 #6 — handles `*`, `**`, and exact\n// paths; not a full gitignore-style parser.\nexport function matchOwner(file: CodeownersFile, repoPath: string): string | null {\n const normalized = repoPath.split(path.sep).join('/')\n for (const rule of file.rules) {\n if (matchesPattern(rule.pattern, normalized)) return rule.owners\n }\n return null\n}\n\nfunction matchesPattern(rawPattern: string, repoPath: string): boolean {\n let pattern = rawPattern.startsWith('/') ? rawPattern.slice(1) : rawPattern\n if (pattern === '*') return !repoPath.includes('/')\n if (pattern === '**' || pattern === '') return true\n // Trailing slash means \"everything in this directory\".\n if (pattern.endsWith('/')) pattern = pattern + '**'\n if (minimatch(repoPath, pattern, { dot: true })) return true\n // A pattern that names a directory should match files beneath it too.\n if (!pattern.includes('*') && minimatch(repoPath, pattern + '/**', { dot: true })) return true\n return false\n}\n\n// Read <serviceDir>/package.json and return the `author` field as a literal\n// string. Accepts either string form (\"Cem D <cem@example.com>\") or object\n// form ({ name: 'Cem D' }). Returns null when missing or unparseable.\n// ADR-054 #2.2.\nexport async function readPackageJsonAuthor(serviceDir: string): Promise<string | null> {\n const pkgPath = path.join(serviceDir, 'package.json')\n if (!(await exists(pkgPath))) return null\n try {\n const pkg = await readJson<PackageJsonAuthor>(pkgPath)\n if (!pkg.author) return null\n if (typeof pkg.author === 'string') return pkg.author\n if (typeof pkg.author === 'object' && typeof pkg.author.name === 'string') return pkg.author.name\n return null\n } catch {\n return null\n }\n}\n\n// Compute owner per ADR-054 priority: CODEOWNERS first, package.json `author`\n// fallback, undefined otherwise. Literal source value, no normalization (#3).\nexport async function computeServiceOwner(\n codeowners: CodeownersFile | null,\n repoPath: string | undefined,\n serviceDir: string,\n): Promise<string | undefined> {\n if (codeowners && repoPath !== undefined) {\n const owner = matchOwner(codeowners, repoPath)\n if (owner) return owner\n }\n const author = await readPackageJsonAuthor(serviceDir)\n return author ?? undefined\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport { parseAllDocuments } from 'yaml'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { recordExtractionError } from './errors.js'\nimport {\n CONFIG_FILE_EXTENSIONS,\n IGNORED_DIRS,\n exists,\n isPythonVenvDir,\n readYaml,\n type DiscoveredService,\n} from './shared.js'\n\n// Populate ServiceNode.aliases from sources that the OTel layer is likely to\n// see in span attributes:\n//\n// - docker-compose service names (compose-DNS).\n// - Dockerfile LABEL values that name the service.\n// - k8s metadata.name (and the cluster-DNS variants) for Service /\n// Deployment / StatefulSet whose name matches a known service.\n//\n// resolveServiceId in ingest.ts checks aliases before falling back to a\n// FRONTIER placeholder; promoteFrontierNodes uses them to retire stale\n// placeholders once they map to a real service.\n\ninterface ComposeService {\n container_name?: string\n hostname?: string\n networks?: string[] | Record<string, unknown>\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\ninterface K8sDoc {\n kind?: string\n metadata?: {\n name?: string\n namespace?: string\n labels?: Record<string, string>\n }\n spec?: {\n selector?: {\n app?: string\n matchLabels?: Record<string, string>\n }\n }\n}\n\nconst K8S_KINDS_WITH_HOSTNAMES = new Set([\n 'Service',\n 'Deployment',\n 'StatefulSet',\n 'DaemonSet',\n])\n\nfunction addAliases(graph: NeatGraph, serviceId: string, candidates: Iterable<string>): void {\n if (!graph.hasNode(serviceId)) return\n const node = graph.getNodeAttributes(serviceId) as ServiceNode & { type?: string }\n if (node.type !== NodeType.ServiceNode) return\n const set = new Set(node.aliases ?? [])\n for (const c of candidates) {\n if (!c) continue\n if (c === node.name) continue\n set.add(c)\n }\n if (set.size === 0) return\n const updated: ServiceNode = { ...node, aliases: [...set].sort() }\n graph.replaceNodeAttributes(serviceId, updated)\n}\n\nfunction indexServicesByName(services: DiscoveredService[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const s of services) {\n map.set(s.node.name, s.node.id)\n map.set(path.basename(s.dir), s.node.id)\n }\n return map\n}\n\nasync function collectComposeAliases(\n graph: NeatGraph,\n scanPath: string,\n serviceIndex: Map<string, string>,\n): Promise<void> {\n let composePath: string | null = null\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(scanPath, name)\n if (await exists(abs)) {\n composePath = abs\n break\n }\n }\n if (!composePath) return\n\n let compose: ComposeFile\n try {\n compose = await readYaml<ComposeFile>(composePath)\n } catch (err) {\n recordExtractionError(\n 'aliases compose',\n path.relative(scanPath, composePath),\n err,\n )\n return\n }\n if (!compose?.services) return\n\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const serviceId = serviceIndex.get(composeName)\n if (!serviceId) continue\n const aliases = new Set<string>([composeName])\n if (svc.container_name) aliases.add(svc.container_name)\n if (svc.hostname) aliases.add(svc.hostname)\n addAliases(graph, serviceId, aliases)\n }\n}\n\nconst LABEL_KEYS = new Set([\n 'service',\n 'service.name',\n 'app',\n 'app.name',\n 'com.docker.compose.service',\n 'org.opencontainers.image.title',\n])\n\nfunction parseDockerfileLabels(content: string): string[] {\n const out: string[] = []\n // Support `LABEL key=value`, `LABEL key=\"value with spaces\"`, and the\n // multi-pair form `LABEL k1=v1 k2=v2`. We don't try to honour line\n // continuations — the common single-line form is enough.\n const lineRegex = /^\\s*label\\s+(.+)$/i\n for (const raw of content.split('\\n')) {\n const m = lineRegex.exec(raw)\n if (!m) continue\n const rest = m[1]!\n const pairRegex = /([\\w.-]+)\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s]+))/g\n let pair: RegExpExecArray | null\n while ((pair = pairRegex.exec(rest)) !== null) {\n const key = pair[1]!.toLowerCase()\n if (!LABEL_KEYS.has(key)) continue\n const value = pair[3] ?? pair[4] ?? pair[5] ?? ''\n if (value) out.push(value)\n }\n }\n return out\n}\n\nasync function collectDockerfileAliases(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<void> {\n for (const service of services) {\n const dockerfilePath = path.join(service.dir, 'Dockerfile')\n if (!(await exists(dockerfilePath))) continue\n let content: string\n try {\n content = await fs.readFile(dockerfilePath, 'utf8')\n } catch (err) {\n recordExtractionError('aliases dockerfile', dockerfilePath, err)\n continue\n }\n const aliases = parseDockerfileLabels(content)\n if (aliases.length > 0) addAliases(graph, service.node.id, aliases)\n }\n}\n\nasync function walkYamlFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkYamlFiles(child, depth + 1, max)))\n } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path.extname(entry.name))) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\nfunction k8sHostnames(name: string, namespace: string | undefined): string[] {\n const ns = namespace ?? 'default'\n return [\n name,\n `${name}.${ns}`,\n `${name}.${ns}.svc`,\n `${name}.${ns}.svc.cluster.local`,\n ]\n}\n\nfunction k8sServiceTarget(\n doc: K8sDoc,\n byName: Map<string, string>,\n): string | null {\n // For `Service` resources, the target is whatever app the spec selects, not\n // necessarily the Service's own metadata.name. We prefer that mapping; if no\n // selector, fall back to the metadata.name match.\n const selector = doc.spec?.selector\n const selectorApp = selector?.app ?? selector?.matchLabels?.app\n if (selectorApp && byName.has(selectorApp)) return byName.get(selectorApp)!\n\n const labelApp = doc.metadata?.labels?.app\n if (labelApp && byName.has(labelApp)) return byName.get(labelApp)!\n\n const metaName = doc.metadata?.name\n if (metaName && byName.has(metaName)) return byName.get(metaName)!\n\n return null\n}\n\nasync function collectK8sAliases(\n graph: NeatGraph,\n scanPath: string,\n serviceIndex: Map<string, string>,\n): Promise<void> {\n const files = await walkYamlFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n let docs: K8sDoc[]\n try {\n docs = parseAllDocuments(content).map((d) => d.toJSON() as K8sDoc)\n } catch {\n continue\n }\n for (const doc of docs) {\n if (!doc?.kind || !doc.metadata?.name) continue\n if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue\n const target = k8sServiceTarget(doc, serviceIndex)\n if (!target) continue\n addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace))\n }\n }\n}\n\nexport async function addServiceAliases(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<void> {\n const byName = indexServicesByName(services)\n await collectComposeAliases(graph, scanPath, byName)\n await collectDockerfileAliases(graph, services)\n await collectK8sAliases(graph, scanPath, byName)\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { EdgeEvidence, ExtractedConfidenceKind, FileNode, GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n fileId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n IGNORED_DIRS,\n SERVICE_FILE_EXTENSIONS,\n isNeatAuthoredSourceFile,\n isPythonVenvDir,\n type DiscoveredService,\n} from '../shared.js'\n\n// Host → owning ServiceNode id, the cross-service resolution the HTTP call-site\n// producers share (ADR-065 #5, ADR-119). A service is reachable by either its\n// directory basename or its manifest name; both map to the same node id. Reused\n// by http.ts (host-level CALLS edges) and route-match.ts (client↔route\n// matching) so the two producers resolve a URL's host to a service identically.\nexport interface ServiceHostIndex {\n knownHosts: Set<string>\n hostToNodeId: Map<string, string>\n}\n\nexport function buildServiceHostIndex(services: DiscoveredService[]): ServiceHostIndex {\n const knownHosts = new Set<string>()\n const hostToNodeId = new Map<string, string>()\n for (const service of services) {\n const base = path.basename(service.dir)\n knownHosts.add(base)\n knownHosts.add(service.pkg.name)\n hostToNodeId.set(base, service.node.id)\n hostToNodeId.set(service.pkg.name, service.node.id)\n }\n return { knownHosts, hostToNodeId }\n}\n\nexport interface SourceFile {\n path: string\n content: string\n}\n\nexport interface ExternalEndpoint {\n // Stable id of the InfraNode this evidence implies. Format\n // `infra:<kind>:<name>` so the orchestrator can dedupe across services.\n infraId: string\n // Display name on the InfraNode (e.g., \"orders\" for kafka-topic:orders).\n name: string\n kind: string\n edgeType: 'CALLS' | 'PUBLISHES_TO' | 'CONSUMES_FROM'\n evidence: EdgeEvidence\n // Confidence grade per ADR-066 — set by the per-shape detector. The\n // orchestrator (calls/index.ts) writes this onto the EXTRACTED edge and\n // applies the precision floor before adding the edge to the graph.\n confidenceKind: ExtractedConfidenceKind\n}\n\nexport async function walkSourceFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (\n entry.isFile() &&\n SERVICE_FILE_EXTENSIONS.has(path.extname(entry.name)) &&\n // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it\n // would attribute our instrumentation imports to the user's service.\n !isNeatAuthoredSourceFile(entry.name)\n ) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\nexport async function loadSourceFiles(dir: string): Promise<SourceFile[]> {\n const paths = await walkSourceFiles(dir)\n const out: SourceFile[] = []\n for (const p of paths) {\n try {\n const content = await fs.readFile(p, 'utf8')\n out.push({ path: p, content })\n } catch {\n // unreadable, skip\n }\n }\n return out\n}\n\n// Locate the line of the first occurrence of `needle` in `text`, 1-indexed.\n// Falls back to line 1 if the needle isn't found verbatim — better to point at\n// the file than to drop the evidence entirely.\nexport function lineOf(text: string, needle: string): number {\n const idx = text.indexOf(needle)\n if (idx < 0) return 1\n return text.slice(0, idx).split('\\n').length\n}\n\nexport function snippet(text: string, line: number): string {\n const lines = text.split('\\n')\n return (lines[line - 1] ?? '').trim()\n}\n\n// Forward-slash a path so a FileNode id is byte-stable across platforms (the\n// `relPath` segment of `file:<service>:<relPath>` must not vary by OS).\nexport function toPosix(p: string): string {\n return p.split('\\\\').join('/')\n}\n\n// Extension → language tag for a FileNode. Returns undefined for extensions we\n// don't name rather than guessing — evidence is never fabricated (§6).\nexport function languageForPath(relPath: string): string | undefined {\n switch (path.extname(relPath).toLowerCase()) {\n case '.py':\n return 'python'\n case '.ts':\n case '.tsx':\n return 'typescript'\n case '.js':\n case '.jsx':\n case '.mjs':\n case '.cjs':\n return 'javascript'\n default:\n return undefined\n }\n}\n\n// File-first emission (file-awareness.md §1–2). Ensure the FileNode for\n// `relPath` and the owning `service ──CONTAINS──▶ file` edge both exist, then\n// return the FileNode id so the caller can originate a relationship from it.\n// `relPath` must already be service-relative and forward-slashed (use toPosix).\n// CONTAINS is structural ownership — graded at the 'structural' tier like\n// CONFIGURED_BY, never a flat value. Idempotent: re-running extraction over an\n// unchanged file is a no-op.\nexport function ensureFileNode(\n graph: NeatGraph,\n serviceName: string,\n serviceNodeId: string,\n relPath: string,\n): { fileNodeId: string; nodesAdded: number; edgesAdded: number } {\n let nodesAdded = 0\n let edgesAdded = 0\n const fileNodeId = fileId(serviceName, relPath)\n if (!graph.hasNode(fileNodeId)) {\n const language = languageForPath(relPath)\n const node: FileNode = {\n id: fileNodeId,\n type: NodeType.FileNode,\n service: serviceName,\n path: relPath,\n ...(language ? { language } : {}),\n discoveredVia: 'static',\n }\n graph.addNode(fileNodeId, node)\n nodesAdded++\n }\n const containsId = extractedEdgeId(serviceNodeId, fileNodeId, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: serviceNodeId,\n target: fileNodeId,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: relPath },\n }\n graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge)\n edgesAdded++\n }\n return { fileNodeId, nodesAdded, edgesAdded }\n}\n","import type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { ensureFileNode, walkSourceFiles, toPosix } from './calls/shared.js'\nimport path from 'node:path'\n\n// Phase 1 — unconditional file enumeration (ADR-092, file-awareness.md §1).\n// Walks every source file matching SERVICE_FILE_EXTENSIONS within each service\n// and emits a FileNode + service ──CONTAINS──▶ file edge, regardless of whether\n// any call pattern fires from the file later.\nexport async function addFiles(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const filePaths = await walkSourceFiles(service.dir)\n for (const filePath of filePaths) {\n const relPath = toPosix(path.relative(service.dir, filePath))\n const { nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relPath,\n )\n nodesAdded += n\n edgesAdded += e\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport Python from 'tree-sitter-python'\nimport type { GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n fileId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, toPosix } from './calls/shared.js'\n\n// Phase 2 — import graph extraction (ADR-092, file-awareness.md §10). Walks\n// every source file's AST for import / require statements and emits IMPORTS\n// edges between FileNodes within the same service. Cross-service and\n// unresolvable specifiers are silent skips — Phase 3's CALLS producers cover\n// the cross-service case where an external-call pattern matches.\n\n// Same chunked-parse approach as calls/shared.ts — tree-sitter's bare string\n// path throws \"Invalid argument\" once the source clears ~32K code units. The\n// callback form streams the source in bounded slices instead.\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\n// ── string-literal text ────────────────────────────────────────────────────\n\n// The interior text of a string literal node, stripped of quote characters.\n// tree-sitter-javascript wraps the content in a `string_fragment` child;\n// fall back to slicing the raw text when that shape isn't present (e.g. an\n// empty string literal has no fragment child at all).\nfunction stringLiteralText(node: Parser.SyntaxNode): string | null {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child?.type === 'string_fragment') return child.text\n }\n const raw = node.text\n if (raw.length >= 2) return raw.slice(1, -1)\n return raw.length === 0 ? null : ''\n}\n\nfunction clipSnippet(text: string): string {\n const oneLine = text.split('\\n')[0] ?? text\n return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine\n}\n\n// ── JS / TS import collection ──────────────────────────────────────────────\n\ninterface RawImport {\n specifier: string\n line: number // 1-indexed\n snippet: string\n}\n\n// Walks the AST for `import ... from 'spec'` / `import 'spec'` (ES modules)\n// and `require('spec')` (CommonJS). Doesn't recurse into import_statement —\n// its only string child is the source specifier, never a nested import.\nfunction collectJsImports(node: Parser.SyntaxNode, out: RawImport[]): void {\n if (node.type === 'import_statement') {\n const source = node.childForFieldName('source')\n if (source) {\n const specifier = stringLiteralText(source)\n if (specifier) {\n out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n return\n }\n\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier' && fn.text === 'require') {\n const args = node.childForFieldName('arguments')\n const firstArg = args?.namedChild(0)\n if (firstArg?.type === 'string') {\n const specifier = stringLiteralText(firstArg)\n if (specifier) {\n out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectJsImports(child, out)\n }\n}\n\n// ── Python import collection ───────────────────────────────────────────────\n\ninterface RawPyImport {\n modulePath: string // dotted path with the leading dots stripped, e.g. 'utils.auth'\n level: number // count of leading dots; 0 = absolute import\n names: string[] // the imported names after `import` — each may be a submodule\n line: number\n snippet: string\n}\n\n// Collects the imported names from the tail of a `from`-import — the part\n// after the `import` keyword. Pushes each `name` (and the original name of an\n// `as`-aliased import); descends into a parenthesised list so `from app import\n// (config, db)` reads the same as the unparenthesised form. A wildcard `*`\n// has no name and is left for the module-file fallback in resolution.\nfunction collectImportedNames(node: Parser.SyntaxNode, out: string[]): void {\n if (node.type === 'aliased_import') {\n const nameNode = node.childForFieldName('name')\n if (nameNode) out.push(nameNode.text)\n return\n }\n if (node.type === 'dotted_name') {\n out.push(node.text)\n return\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectImportedNames(child, out)\n }\n}\n\n// Walks the AST for `from X import a, b` / `from .X import a`. Bare `import X`\n// statements name modules without resolving to a single file (`import os.path`\n// doesn't tell us which file in `os` got used), so Phase 2 limits itself to\n// the `from`-form per the resolution rules in the contract. The imported names\n// matter: `from app import config` names the `config` submodule, not a symbol\n// on `app/__init__.py`, so resolution keys on them (see resolvePyImport).\nfunction collectPyImports(node: Parser.SyntaxNode, out: RawPyImport[]): void {\n if (node.type === 'import_from_statement') {\n let level = 0\n let modulePath = ''\n const names: string[] = []\n let pastFrom = false\n let pastImport = false\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (!child) continue\n if (!pastFrom) {\n if (child.type === 'from') pastFrom = true\n continue\n }\n if (!pastImport) {\n if (child.type === 'import') {\n pastImport = true\n continue\n }\n // The `from`-module spec, before the `import` keyword.\n if (child.type === 'relative_import') {\n for (let j = 0; j < child.childCount; j++) {\n const rc = child.child(j)\n if (!rc) continue\n // tree-sitter-python groups every leading dot into a single\n // import_prefix node — `..` parses as one import_prefix with two\n // `.` children, not two import_prefix nodes. Count the `.` tokens,\n // not the prefix nodes, or multi-dot imports under-count `level`\n // and resolve onto the wrong (sometimes self) target (#457).\n if (rc.type === 'import_prefix') {\n for (let k = 0; k < rc.childCount; k++) {\n if (rc.child(k)?.type === '.') level++\n }\n } else if (rc.type === 'dotted_name') modulePath = rc.text\n }\n } else if (child.type === 'dotted_name') {\n modulePath = child.text\n }\n continue\n }\n // Past the `import` keyword — the imported names.\n collectImportedNames(child, names)\n }\n\n if (level > 0 || modulePath) {\n out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectPyImports(child, out)\n }\n}\n\n// ── filesystem resolution helpers ──────────────────────────────────────────\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// An import that escapes the service directory (`../../other-service/x`)\n// isn't an intra-service edge — Phase 2 is scoped to within-service module\n// dependencies (file-awareness.md §10).\nfunction isWithinServiceDir(candidate: string, serviceDir: string): boolean {\n const rel = path.relative(serviceDir, candidate)\n return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel)\n}\n\nconst JS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']\nconst JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`)\n\n// Try `base`, `base.<ext>`, and `base/index.<ext>` in TypeScript-resolution\n// order. Returns the service-relative posix path of the first hit.\nasync function firstExistingCandidate(\n base: string,\n serviceDir: string,\n): Promise<string | null> {\n for (const ext of JS_EXTENSIONS) {\n const candidate = base + ext\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n return toPosix(path.relative(serviceDir, candidate))\n }\n }\n for (const indexFile of JS_INDEX_FILES) {\n const candidate = path.join(base, indexFile)\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n return toPosix(path.relative(serviceDir, candidate))\n }\n }\n return null\n}\n\ninterface TsPathConfig {\n paths: Record<string, string[]>\n baseDir: string // absolute directory compilerOptions.baseUrl resolves against\n}\n\n// `tsconfig.json` at the service root. The contract names scanPath as a\n// fallback location too, but every discovered service already carries its own\n// root — the scanPath fallback only matters for configs that live above the\n// service tree, which the resolver doesn't have a path back to from here.\nasync function loadTsPathConfig(serviceDir: string): Promise<TsPathConfig | null> {\n const tsconfigPath = path.join(serviceDir, 'tsconfig.json')\n let raw: string\n try {\n raw = await fs.readFile(tsconfigPath, 'utf8')\n } catch {\n return null\n }\n try {\n const parsed = JSON.parse(raw) as {\n compilerOptions?: { paths?: Record<string, string[]>; baseUrl?: string }\n }\n const paths = parsed.compilerOptions?.paths\n if (!paths || Object.keys(paths).length === 0) return null\n const baseUrl = parsed.compilerOptions?.baseUrl\n return { paths, baseDir: baseUrl ? path.resolve(serviceDir, baseUrl) : serviceDir }\n } catch (err) {\n recordExtractionError('import alias resolution', tsconfigPath, err)\n return null\n }\n}\n\n// Resolve a bare specifier against `compilerOptions.paths`. Matches the first\n// alias whose pattern fits — exact (`@db`) or wildcard (`@db/*`) — and tries\n// each mapped target in declaration order. No match anywhere → null, the\n// silent-skip the contract calls for.\nasync function resolveTsAlias(\n specifier: string,\n config: TsPathConfig,\n serviceDir: string,\n): Promise<string | null> {\n for (const [pattern, targets] of Object.entries(config.paths)) {\n let suffix: string | null = null\n if (pattern === specifier) {\n suffix = ''\n } else if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1) // keep the trailing '/'\n if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length)\n }\n if (suffix === null) continue\n\n for (const target of targets) {\n const targetBase = target.endsWith('/*') ? target.slice(0, -2) : target.replace(/\\*$/, '')\n const resolvedBase = path.resolve(config.baseDir, targetBase, suffix)\n const hit = await firstExistingCandidate(resolvedBase, serviceDir)\n if (hit) return hit\n // The candidate may already carry its own extension (`@db/mongo.ts`).\n if (isWithinServiceDir(resolvedBase, serviceDir) && (await fileExists(resolvedBase))) {\n return toPosix(path.relative(serviceDir, resolvedBase))\n }\n }\n }\n return null\n}\n\n// Resolves a JS/TS module specifier to a service-relative posix path, or\n// null when it names something outside the service (node_modules, Node\n// builtins, an alias with no tsconfig match, an escaping relative path).\nasync function resolveJsImport(\n specifier: string,\n importerDir: string,\n serviceDir: string,\n tsPaths: TsPathConfig | null,\n): Promise<string | null> {\n if (!specifier) return null\n\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const base = path.resolve(importerDir, specifier)\n const ext = path.extname(specifier)\n\n if (ext) {\n // `./foo.js` written against a `.ts` source — the TypeScript ESM\n // convention of naming the post-build extension in source. Try the\n // TS sibling before the literal path.\n if (ext === '.js' || ext === '.jsx') {\n const tsExt = ext === '.jsx' ? '.tsx' : '.ts'\n const tsSibling = base.slice(0, -ext.length) + tsExt\n if (isWithinServiceDir(tsSibling, serviceDir) && (await fileExists(tsSibling))) {\n return toPosix(path.relative(serviceDir, tsSibling))\n }\n }\n if (isWithinServiceDir(base, serviceDir) && (await fileExists(base))) {\n return toPosix(path.relative(serviceDir, base))\n }\n return null\n }\n\n return firstExistingCandidate(base, serviceDir)\n }\n\n // Bare specifier — only a registered TS path alias can make this\n // intra-service. Anything else is node_modules / a Node builtin.\n if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir)\n return null\n}\n\n// Resolves a Python `from`-import to the service-relative posix paths it\n// touches — one per imported module. Relative imports (level > 0) walk up from\n// the importing module's directory; absolute imports match against the service\n// source tree directly.\n//\n// `from app import config` names the `config` *submodule* (app/config.py), not\n// a symbol re-exported by app/__init__.py. Resolving the whole statement onto\n// the package's __init__.py was wrong: every intra-package import collapsed\n// onto one target and the per-(source,target) edge dedup then made the second\n// such dependency invisible. So we key on each imported name, resolving it to\n// its own module file, and fall back to the package/module file only for names\n// that aren't submodules (plain symbols, or a `*` wildcard).\nasync function resolvePyImport(\n imp: RawPyImport,\n importerPath: string,\n serviceDir: string,\n): Promise<string[]> {\n let baseDir: string\n if (imp.level > 0) {\n baseDir = path.dirname(importerPath)\n for (let i = 1; i < imp.level; i++) baseDir = path.dirname(baseDir)\n } else {\n baseDir = serviceDir\n }\n\n // The on-disk base of the `from`-module. `from app import x` → app/ ;\n // `from . import x` → the importer's own package directory (modulePath empty).\n const moduleBase = imp.modulePath\n ? path.join(baseDir, imp.modulePath.split('.').join('/'))\n : baseDir\n\n const resolved = new Set<string>()\n let needModuleFile = imp.names.length === 0 // wildcard / unparsed → the module itself\n\n for (const name of imp.names) {\n const submoduleFile = path.join(moduleBase, `${name}.py`)\n const subpackageInit = path.join(moduleBase, name, '__init__.py')\n if (isWithinServiceDir(submoduleFile, serviceDir) && (await fileExists(submoduleFile))) {\n resolved.add(toPosix(path.relative(serviceDir, submoduleFile)))\n } else if (isWithinServiceDir(subpackageInit, serviceDir) && (await fileExists(subpackageInit))) {\n resolved.add(toPosix(path.relative(serviceDir, subpackageInit)))\n } else {\n // Not a submodule — the name is a symbol defined in the module itself.\n needModuleFile = true\n }\n }\n\n if (needModuleFile) {\n // An empty modulePath (`from . import x`) has no module file of its own —\n // the package is the directory, so only its __init__.py applies.\n const moduleFileCandidates = imp.modulePath\n ? [`${moduleBase}.py`, path.join(moduleBase, '__init__.py')]\n : [path.join(moduleBase, '__init__.py')]\n for (const candidate of moduleFileCandidates) {\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n resolved.add(toPosix(path.relative(serviceDir, candidate)))\n break\n }\n }\n }\n\n return [...resolved]\n}\n\n// ── edge emission ──────────────────────────────────────────────────────────\n\nfunction emitImportEdge(\n graph: NeatGraph,\n serviceName: string,\n importerFileId: string,\n importerRelPath: string,\n importeeRelPath: string,\n line: number,\n snippet: string,\n): number {\n const importeeFileId = fileId(serviceName, importeeRelPath)\n // Phase 1 enumerates every source file unconditionally; a resolved path\n // that isn't a FileNode is outside SERVICE_FILE_EXTENSIONS or otherwise\n // wasn't walked — not an intra-service module edge.\n if (!graph.hasNode(importeeFileId)) return 0\n\n const edgeId = extractedEdgeId(importerFileId, importeeFileId, EdgeType.IMPORTS)\n if (graph.hasEdge(edgeId)) return 0\n\n const edge: GraphEdge = {\n id: edgeId,\n source: importerFileId,\n target: importeeFileId,\n type: EdgeType.IMPORTS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: importerRelPath, line, snippet },\n }\n graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge)\n return 1\n}\n\n// ── producer ───────────────────────────────────────────────────────────────\n\nexport async function addImports(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const pyParser = makePyParser()\n let edgesAdded = 0\n\n for (const service of services) {\n const tsPaths = await loadTsPathConfig(service.dir)\n const files = await loadSourceFiles(service.dir)\n\n for (const file of files) {\n // ADR-065 §1 — test-scope exclusion. The file stays a FileNode (Phase 1);\n // only its outbound module edges are filtered (file-awareness.md §10).\n if (isTestPath(file.path)) continue\n\n const relFile = toPosix(path.relative(service.dir, file.path))\n const importerFileId = fileId(service.pkg.name, relFile)\n const isPython = path.extname(file.path) === '.py'\n\n if (isPython) {\n let pyImports: RawPyImport[] = []\n try {\n const tree = parseSource(pyParser, file.content)\n collectPyImports(tree.rootNode, pyImports)\n } catch (err) {\n recordExtractionError('import extraction', file.path, err)\n continue\n }\n for (const imp of pyImports) {\n const resolvedPaths = await resolvePyImport(imp, file.path, service.dir)\n for (const resolved of resolvedPaths) {\n edgesAdded += emitImportEdge(\n graph,\n service.pkg.name,\n importerFileId,\n relFile,\n resolved,\n imp.line,\n imp.snippet,\n )\n }\n }\n continue\n }\n\n let jsImports: RawImport[] = []\n try {\n const tree = parseSource(jsParser, file.content)\n collectJsImports(tree.rootNode, jsImports)\n } catch (err) {\n recordExtractionError('import extraction', file.path, err)\n continue\n }\n for (const imp of jsImports) {\n const resolved = await resolveJsImport(imp.specifier, path.dirname(file.path), service.dir, tsPaths)\n if (!resolved) continue\n edgesAdded += emitImportEdge(\n graph,\n service.pkg.name,\n importerFileId,\n relFile,\n resolved,\n imp.line,\n imp.snippet,\n )\n }\n }\n }\n\n return { nodesAdded: 0, edgesAdded }\n}\n","import path from 'node:path'\nimport type {\n CompatibleDriver,\n ConfigNode,\n DatabaseNode,\n GraphEdge,\n GraphNode,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n configId,\n databaseId,\n confidenceForExtracted,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n deprecatedApis,\n nodeEngineConstraints,\n packageConflicts,\n} from '../../compat.js'\nimport {\n cleanVersion,\n isConfigFile,\n makeEdgeId,\n type DiscoveredService,\n} from '../shared.js'\nimport { ensureFileNode, toPosix } from '../calls/shared.js'\nimport { dbConfigYamlParser } from './db-config-yaml.js'\nimport { dotenvParser } from './dotenv.js'\nimport { prismaParser } from './prisma.js'\nimport { drizzleParser } from './drizzle.js'\nimport { knexParser } from './knex.js'\nimport { ormconfigParser } from './ormconfig.js'\nimport { typeormParser } from './typeorm.js'\nimport { sequelizeParser } from './sequelize.js'\nimport { dockerComposeParser } from './docker-compose.js'\nimport type { DbConfig } from './shared.js'\n\nexport type { DbConfig } from './shared.js'\n\nexport interface DbParser {\n name: string\n parse(serviceDir: string): Promise<DbConfig[]>\n}\n\n// Registry — order is for tie-breaking only (first wins on identical host).\n// db-config.yaml stays first so the canonical demo behaviour matches today's\n// extraction byte-for-byte.\nexport const DB_PARSERS: DbParser[] = [\n dbConfigYamlParser,\n dotenvParser,\n prismaParser,\n drizzleParser,\n knexParser,\n ormconfigParser,\n typeormParser,\n sequelizeParser,\n dockerComposeParser,\n]\n\nfunction compatibleDriversFor(engine: string): CompatibleDriver[] {\n return compatPairs()\n .filter((p) => p.engine === engine)\n .map((p) => ({ name: p.driver, minVersion: p.minDriverVersion }))\n}\n\nfunction toDatabaseNode(config: DbConfig): DatabaseNode {\n return {\n id: databaseId(config.host),\n type: NodeType.DatabaseNode,\n name: config.database || config.host,\n engine: config.engine,\n engineVersion: config.engineVersion,\n compatibleDrivers: compatibleDriversFor(config.engine),\n host: config.host,\n port: config.port,\n }\n}\n\nexport function attachIncompatibilities(\n service: DiscoveredService,\n configs: DbConfig[],\n): void {\n const deps = { ...(service.pkg.dependencies ?? {}), ...(service.pkg.devDependencies ?? {}) }\n const incompatibilities: NonNullable<ServiceNode['incompatibilities']> = []\n const seen = new Set<string>()\n\n // 1. driver-engine — original behaviour. Per (db config, configured driver\n // pair) check that the declared driver version meets the engine threshold.\n for (const config of configs) {\n for (const pair of compatPairs()) {\n if (pair.engine !== config.engine) continue\n const declaredVersion = cleanVersion(deps[pair.driver])\n if (!declaredVersion) continue\n const result = checkCompatibility(\n pair.driver,\n declaredVersion,\n config.engine,\n config.engineVersion,\n )\n if (!result.compatible && result.reason) {\n const key = `driver-engine|${pair.driver}@${declaredVersion}|${config.engine}@${config.engineVersion}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'driver-engine',\n driver: pair.driver,\n driverVersion: declaredVersion,\n engine: config.engine,\n engineVersion: config.engineVersion,\n reason: result.reason,\n })\n }\n }\n }\n\n // 2. node-engine — service's `engines.node` vs each declared dep that has a\n // matrix-recorded minimum.\n const serviceNodeEngine = service.node.nodeEngine ?? service.pkg.engines?.node\n for (const constraint of nodeEngineConstraints()) {\n const declared = cleanVersion(deps[constraint.package])\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine)\n if (!result.compatible && result.reason) {\n const key = `node-engine|${constraint.package}@${declared}|${serviceNodeEngine ?? ''}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'node-engine',\n package: constraint.package,\n packageVersion: declared,\n requiredNodeVersion: result.requiredNodeVersion ?? constraint.minNodeVersion,\n ...(serviceNodeEngine ? { declaredNodeEngine: serviceNodeEngine } : {}),\n reason: result.reason,\n })\n }\n }\n\n // 3. package-conflict — pair like react-query 5+ requiring react 18+.\n for (const conflict of packageConflicts()) {\n const declared = cleanVersion(deps[conflict.package])\n if (!declared) continue\n const requiredVersion = cleanVersion(deps[conflict.requires.name])\n const result = checkPackageConflict(conflict, declared, requiredVersion)\n if (!result.compatible && result.reason) {\n const key = `package-conflict|${conflict.package}@${declared}|${conflict.requires.name}@${requiredVersion ?? 'missing'}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'package-conflict',\n package: conflict.package,\n packageVersion: declared,\n requires: conflict.requires,\n ...(requiredVersion ? { foundVersion: requiredVersion } : {}),\n reason: result.reason,\n })\n }\n }\n\n // 4. deprecated-api — flag presence of a known-deprecated package.\n for (const rule of deprecatedApis()) {\n const declared = cleanVersion(deps[rule.package])\n if (declared === undefined) continue\n const result = checkDeprecatedApi(rule, declared)\n if (!result.compatible && result.reason) {\n const key = `deprecated-api|${rule.package}@${declared}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'deprecated-api',\n package: rule.package,\n packageVersion: declared,\n reason: result.reason,\n })\n }\n }\n\n if (incompatibilities.length > 0) service.node.incompatibilities = incompatibilities\n}\n\n// Phase 2 — for each service, run every parser and merge their DbConfigs by\n// host. Each unique host produces one DatabaseNode + CONNECTS_TO edge from the\n// service. The parser registry decides priority on tie; the demo's\n// db-config.yaml stays first so its `engineVersion: 15` continues to win.\nexport async function addDatabasesAndCompat(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const merged = new Map<string, DbConfig>()\n for (const parser of DB_PARSERS) {\n let configs: DbConfig[]\n try {\n configs = await parser.parse(service.dir)\n } catch (err) {\n console.warn(\n `[neat] ${parser.name} parser failed on ${service.node.name}: ${(err as Error).message}`,\n )\n continue\n }\n for (const config of configs) {\n if (!config.host) continue\n if (!merged.has(config.host)) merged.set(config.host, config)\n }\n }\n\n const allConfigs = [...merged.values()]\n for (const config of allConfigs) {\n const dbNode = toDatabaseNode(config)\n if (!graph.hasNode(dbNode.id)) {\n graph.addNode(dbNode.id, { ...dbNode, discoveredVia: 'static' })\n nodesAdded++\n } else {\n // OTel ingest may have auto-created a minimal node at this id. Merge\n // per ADR-033: static fields override OTel-derived fields, discoveredVia\n // flips to 'merged' when both layers contributed.\n const existing = graph.getNodeAttributes(dbNode.id) as DatabaseNode\n const mergedDiscoveredVia: 'static' | 'otel' | 'merged' =\n existing.discoveredVia === 'otel' ? 'merged' : 'static'\n graph.replaceNodeAttributes(dbNode.id, {\n ...existing,\n ...dbNode,\n discoveredVia: mergedDiscoveredVia,\n })\n }\n // file-awareness §1 — the connection is declared in a config file; that\n // file is the relationship origin. ensureFileNode creates the FileNode +\n // CONTAINS edge so the CONNECTS_TO lands file-grained, not service-level.\n const relConfigFile = toPosix(path.relative(service.dir, config.sourceFile))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relConfigFile,\n )\n nodesAdded += fn\n edgesAdded += fe\n const evidenceFile = toPosix(path.relative(scanPath, config.sourceFile))\n const edge: GraphEdge = {\n id: makeEdgeId(fileNodeId, dbNode.id, EdgeType.CONNECTS_TO),\n source: fileNodeId,\n target: dbNode.id,\n type: EdgeType.CONNECTS_TO,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile },\n }\n if (!graph.hasEdge(edge.id)) {\n graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n\n // Service-level declared DB target. When a service declares exactly one DB\n // connection in config — a `postgres://…` string in .env, a db-config.yaml,\n // a prisma/drizzle/knex datasource — record where it's pointed on the\n // ServiceNode as `dbConnectionTarget` (host[:port]) and link the service to\n // the config that declared it with an EXTRACTED CONFIGURED_BY edge. This is\n // the service-grained declared intent the host-mismatch divergence compares\n // against the service-grained OBSERVED CONNECTS_TO (file-awareness §7 —\n // compare at the shared grain; a DB OTel span carries no call site, so the\n // comparison is service-level). Without this, `declaredHostFor` reads an\n // unpopulated field and host-mismatch never fires.\n //\n // We hold to a *single* declared target: with two or more distinct hosts\n // the single-target model is ambiguous and would flag every observed host\n // but one as a false mismatch, so we decline rather than guess.\n if (allConfigs.length === 1) {\n const primary = allConfigs[0]!\n service.node.dbConnectionTarget = primary.port\n ? `${primary.host}:${primary.port}`\n : primary.host\n\n // Link to the config file that declared the connection. Mirror configs.ts's\n // ConfigNode id (Phase 3 runs after this and is idempotent on the node);\n // the CONFIGURED_BY edge is service-grained here, matching the grain of\n // the declared target it backs.\n const relPath = path.relative(scanPath, primary.sourceFile)\n const cfgId = configId(relPath)\n if (!graph.hasNode(cfgId)) {\n const cfgNode: ConfigNode = {\n id: cfgId,\n type: NodeType.ConfigNode,\n name: path.basename(primary.sourceFile),\n path: relPath,\n fileType: isConfigFile(path.basename(primary.sourceFile)).fileType || 'config',\n }\n graph.addNode(cfgId, cfgNode)\n nodesAdded++\n }\n const cfgEdge: GraphEdge = {\n id: makeEdgeId(service.node.id, cfgId, EdgeType.CONFIGURED_BY),\n source: service.node.id,\n target: cfgId,\n type: EdgeType.CONFIGURED_BY,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: toPosix(relPath) },\n }\n if (!graph.hasEdge(cfgEdge.id)) {\n graph.addEdgeWithKey(cfgEdge.id, cfgEdge.source, cfgEdge.target, cfgEdge)\n edgesAdded++\n }\n }\n\n // Run all kinds of incompat checks even for services with no db connection\n // — node-engine / package-conflict / deprecated-api don't depend on db.\n attachIncompatibilities(service, allConfigs)\n if (graph.hasNode(service.node.id)) {\n // Merge with whatever's on the node already (aliases from γ #75 land\n // before this phase), so the writeback doesn't drop fields populated by\n // earlier passes.\n const current = graph.getNodeAttributes(service.node.id) as ServiceNode\n const updated: ServiceNode = {\n ...current,\n ...(service.node as ServiceNode),\n ...(current.aliases ? { aliases: current.aliases } : {}),\n }\n // attachIncompatibilities only sets the field when there's something to\n // flag. On a re-extract (`neat watch`, `POST /graph/scan`), a stale\n // entry on `current` would otherwise survive the spread and leave the\n // graph reporting a problem the new manifest no longer has.\n if (!service.node.incompatibilities || service.node.incompatibilities.length === 0) {\n delete (updated as { incompatibilities?: unknown }).incompatibilities\n }\n // Same stale-survivor guard for the declared DB target: if this pass found\n // no single declared connection, a value left on `current` from a prior\n // extract would otherwise survive the spread.\n if (!service.node.dbConnectionTarget) {\n delete (updated as { dbConnectionTarget?: unknown }).dbConnectionTarget\n }\n graph.replaceNodeAttributes(service.node.id, updated as unknown as GraphNode)\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { exists, readYaml } from '../shared.js'\nimport type { DbConfig } from './shared.js'\n\ninterface DbConfigYaml {\n host: string\n port?: number\n database: string\n engine: string\n engineVersion?: string | number\n}\n\n// The original db-config.yaml format, kept as a parser so the demo continues\n// to work. Engine + version are explicit here, so this is the one source that\n// can produce a real engineVersion without inference.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const yamlPath = path.join(serviceDir, 'db-config.yaml')\n if (!(await exists(yamlPath))) return []\n const raw = await readYaml<DbConfigYaml>(yamlPath)\n return [\n {\n host: raw.host,\n port: raw.port,\n database: raw.database,\n engine: raw.engine,\n engineVersion: raw.engineVersion !== undefined ? String(raw.engineVersion) : 'unknown',\n sourceFile: yamlPath,\n },\n ]\n}\n\nexport const dbConfigYamlParser = { name: 'db-config.yaml', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { isConfigFile } from '../shared.js'\nimport { parseConnectionString, type DbConfig } from './shared.js'\n\nconst CONNECTION_KEYS = new Set([\n 'DATABASE_URL',\n 'DB_URL',\n 'POSTGRES_URL',\n 'POSTGRESQL_URL',\n 'MYSQL_URL',\n 'MONGODB_URI',\n 'MONGO_URL',\n 'MONGO_URI',\n 'REDIS_URL',\n])\n\n// Per ADR-016, .env contents do not land in any snapshot. We read them here\n// only to derive a transient DbConfig — the value never reaches a ConfigNode.\nfunction parseDotenvLine(line: string): { key: string; value: string } | null {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) return null\n const eq = trimmed.indexOf('=')\n if (eq < 0) return null\n const key = trimmed.slice(0, eq).trim()\n let value = trimmed.slice(eq + 1).trim()\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n return { key, value }\n}\n\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const entries = await fs.readdir(serviceDir, { withFileTypes: true }).catch(() => [])\n const configs: DbConfig[] = []\n const seen = new Set<string>()\n\n for (const entry of entries) {\n if (!entry.isFile()) continue\n const match = isConfigFile(entry.name)\n if (!match.match || match.fileType !== 'env') continue\n\n const filePath = path.join(serviceDir, entry.name)\n const content = await fs.readFile(filePath, 'utf8')\n for (const line of content.split('\\n')) {\n const parsed = parseDotenvLine(line)\n if (!parsed) continue\n if (!CONNECTION_KEYS.has(parsed.key.toUpperCase())) continue\n const config = parseConnectionString(parsed.value)\n if (!config) continue\n const key = `${config.engine}://${config.host}:${config.port ?? ''}/${config.database}`\n if (seen.has(key)) continue\n seen.add(key)\n configs.push({ ...config, sourceFile: filePath })\n }\n }\n return configs\n}\n\nexport const dotenvParser = { name: '.env', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport interface DbConfig {\n host: string\n port?: number\n database: string\n engine: string\n engineVersion: string // \"unknown\" when not statically determinable\n // Absolute path to the file the parser read this config from. Required —\n // ghost-edge cleanup keys CONNECTS_TO retirement on `evidence.file` per\n // ADR-032 / #140. Parsers that synthesize a DbConfig from a partial\n // source still set this to the file the partial came from.\n sourceFile: string\n}\n\n// Map a connection-string scheme to the engine name our compat matrix uses.\n// Schemes like \"postgres+asyncpg\" are normalised by stripping the dialect\n// suffix; anything we don't recognise returns null so the parser can decline.\nexport function schemeToEngine(scheme: string): string | null {\n const s = scheme.toLowerCase().split('+')[0]\n switch (s) {\n case 'postgres':\n case 'postgresql':\n return 'postgresql'\n case 'mysql':\n case 'mariadb':\n return 'mysql'\n case 'mongodb':\n case 'mongodb+srv':\n return 'mongodb'\n case 'redis':\n case 'rediss':\n return 'redis'\n case 'sqlite':\n return 'sqlite'\n default:\n return null\n }\n}\n\n// Returns the inner DbConfig fields without sourceFile — every caller spreads\n// the parser's own file path on top. Type makes that contract explicit.\nexport type ParsedConnectionConfig = Omit<DbConfig, 'sourceFile'>\n\nexport function parseConnectionString(url: string): ParsedConnectionConfig | null {\n const m = url.match(\n /^(?<scheme>[a-z][a-z+]*):\\/\\/(?:[^@/]+(?::[^@]*)?@)?(?<host>[^:/?]+)(?::(?<port>\\d+))?(?:\\/(?<db>[^?#]*))?/i,\n )\n if (!m || !m.groups) return null\n const engine = schemeToEngine(m.groups.scheme!)\n if (!engine) return null\n return {\n host: m.groups.host!,\n port: m.groups.port ? Number(m.groups.port) : undefined,\n database: m.groups.db ?? '',\n engine,\n engineVersion: 'unknown',\n }\n}\n\nexport async function readIfExists(filePath: string): Promise<string | null> {\n try {\n return await fs.readFile(filePath, 'utf8')\n } catch {\n return null\n }\n}\n\nexport async function findFirst(\n serviceDir: string,\n candidates: string[],\n): Promise<string | null> {\n for (const rel of candidates) {\n const abs = path.join(serviceDir, rel)\n const content = await readIfExists(abs)\n if (content !== null) return abs\n }\n return null\n}\n\n// Engine name from a docker-compose `image:` value like \"postgres:15-alpine\"\n// or \"mysql/mysql-server:8.0\". Returns the engine + version when both are\n// resolvable, or null if the image isn't one we recognise.\nexport function engineFromImage(\n image: string,\n): { engine: string; engineVersion: string } | null {\n const lower = image.toLowerCase()\n const colon = lower.lastIndexOf(':')\n const repo = colon >= 0 ? lower.slice(0, colon) : lower\n const tag = colon >= 0 ? lower.slice(colon + 1) : 'latest'\n const last = repo.split('/').pop() ?? repo\n let engine: string | null = null\n if (last.startsWith('postgres')) engine = 'postgresql'\n else if (last.startsWith('mysql') || last.startsWith('mariadb')) engine = 'mysql'\n else if (last.startsWith('mongo')) engine = 'mongodb'\n else if (last.startsWith('redis')) engine = 'redis'\n else if (last.startsWith('sqlite')) engine = 'sqlite'\n if (!engine) return null\n // Strip everything after the major version digit run; \"15-alpine\" -> \"15\".\n const versionMatch = tag.match(/^(\\d+(?:\\.\\d+){0,2})/)\n return {\n engine,\n engineVersion: versionMatch ? versionMatch[1]! : 'unknown',\n }\n}\n","import path from 'node:path'\nimport { readIfExists, parseConnectionString, schemeToEngine, type DbConfig } from './shared.js'\n\n// Prisma's schema file declares datasources of the form:\n//\n// datasource db {\n// provider = \"postgresql\"\n// url = env(\"DATABASE_URL\")\n// }\n//\n// We match the provider directly. URLs come in via env() so we can't resolve\n// them statically; host/database fall back to placeholders so the DatabaseNode\n// id remains deterministic per service.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const schemaPath = path.join(serviceDir, 'prisma', 'schema.prisma')\n const content = await readIfExists(schemaPath)\n if (!content) return []\n\n const block = content.match(/datasource\\s+\\w+\\s*\\{([^}]*)\\}/s)\n if (!block) return []\n const body = block[1] ?? ''\n\n const providerMatch = body.match(/provider\\s*=\\s*\"([^\"]+)\"/)\n if (!providerMatch) return []\n const engine = schemeToEngine(providerMatch[1]!)\n if (!engine) return []\n\n const urlMatch = body.match(/url\\s*=\\s*\"([^\"]+)\"/)\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: schemaPath }]\n }\n\n return [\n {\n host: `${engine}-prisma`,\n database: '',\n engine,\n engineVersion: 'unknown',\n sourceFile: schemaPath,\n },\n ]\n}\n\nexport const prismaParser = { name: 'prisma', parse }\n","import { findFirst, readIfExists, parseConnectionString, schemeToEngine, type DbConfig } from './shared.js'\n\nconst DIALECT_TO_ENGINE: Record<string, string> = {\n postgresql: 'postgresql',\n postgres: 'postgresql',\n pg: 'postgresql',\n mysql: 'mysql',\n mysql2: 'mysql',\n sqlite: 'sqlite',\n 'better-sqlite': 'sqlite',\n}\n\n// Drizzle's drizzle.config.{ts,js,mjs} declares a `dialect` plus credentials.\n// We can't safely eval the file, but the relevant bits are simple key/value\n// expressions — regex-extracted to stay sandbox-clean and dep-free.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'drizzle.config.ts',\n 'drizzle.config.js',\n 'drizzle.config.mjs',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const dialectMatch = content.match(/dialect\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (!dialectMatch) return []\n const engine =\n DIALECT_TO_ENGINE[dialectMatch[1]!.toLowerCase()] ?? schemeToEngine(dialectMatch[1]!)\n if (!engine) return []\n\n const urlMatch = content.match(\n /(?:url|connectionString)\\s*:\\s*['\"`]([a-z][a-z+]*:\\/\\/[^'\"`]+)['\"`]/i,\n )\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n const hostMatch = content.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (hostMatch) {\n const portMatch = content.match(/port\\s*:\\s*(\\d+)/)\n const dbMatch = content.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n return [\n {\n host: hostMatch[1]!,\n port: portMatch ? Number(portMatch[1]) : undefined,\n database: dbMatch?.[1] ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n }\n return [\n { host: `${engine}-drizzle`, database: '', engine, engineVersion: 'unknown', sourceFile: filePath },\n ]\n}\n\nexport const drizzleParser = { name: 'drizzle', parse }\n","import { findFirst, readIfExists, parseConnectionString, type DbConfig } from './shared.js'\n\nconst CLIENT_TO_ENGINE: Record<string, string> = {\n pg: 'postgresql',\n postgres: 'postgresql',\n postgresql: 'postgresql',\n mysql: 'mysql',\n mysql2: 'mysql',\n sqlite3: 'sqlite',\n 'better-sqlite3': 'sqlite',\n}\n\n// knexfile.{js,ts} declares a client (one of pg/mysql/sqlite/...) plus a\n// connection string or host/port object. We pick whichever shape is in the\n// file and ignore environment-driven values we can't resolve statically.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'knexfile.js',\n 'knexfile.ts',\n 'knexfile.cjs',\n 'knexfile.mjs',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const clientMatch = content.match(/client\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (!clientMatch) return []\n const engine = CLIENT_TO_ENGINE[clientMatch[1]!.toLowerCase()]\n if (!engine) return []\n\n const urlMatch = content.match(\n /connection\\s*:\\s*['\"`]([a-z][a-z+]*:\\/\\/[^'\"`]+)['\"`]/i,\n )\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n\n const host = content.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1]\n if (host) {\n const port = content.match(/port\\s*:\\s*(\\d+)/)?.[1]\n const database = content.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1] ?? ''\n return [\n {\n host,\n port: port ? Number(port) : undefined,\n database,\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n }\n\n return [{ host: `${engine}-knex`, database: '', engine, engineVersion: 'unknown', sourceFile: filePath }]\n}\n\nexport const knexParser = { name: 'knex', parse }\n","import path from 'node:path'\nimport { exists, readJson, readYaml } from '../shared.js'\nimport { schemeToEngine, type DbConfig } from './shared.js'\n\ninterface OrmConfigEntry {\n type?: string\n host?: string\n port?: number\n database?: string\n}\n\n// ormconfig.{json,yaml,yml} — TypeORM's legacy config file. Single object or\n// an array of named connections; we walk both.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n for (const candidate of ['ormconfig.json', 'ormconfig.yaml', 'ormconfig.yml']) {\n const abs = path.join(serviceDir, candidate)\n if (!(await exists(abs))) continue\n const raw = candidate.endsWith('.json')\n ? await readJson<OrmConfigEntry | OrmConfigEntry[]>(abs)\n : await readYaml<OrmConfigEntry | OrmConfigEntry[]>(abs)\n const entries = Array.isArray(raw) ? raw : [raw]\n\n const out: DbConfig[] = []\n for (const entry of entries) {\n if (!entry?.type || !entry.host) continue\n const engine = schemeToEngine(entry.type)\n if (!engine) continue\n out.push({\n host: entry.host,\n port: entry.port,\n database: entry.database ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: abs,\n })\n }\n if (out.length > 0) return out\n }\n return []\n}\n\nexport const ormconfigParser = { name: 'ormconfig', parse }\n","import { findFirst, readIfExists, schemeToEngine, type DbConfig } from './shared.js'\n\n// TypeORM's modern shape: `new DataSource({ type: 'postgres', host, port, ... })`\n// in a data-source.ts (or .js). We regex for the type/host/port/database keys\n// in the first DataSource literal we find.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'data-source.ts',\n 'data-source.js',\n 'src/data-source.ts',\n 'src/data-source.js',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const block = content.match(/new\\s+DataSource\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)/)\n const body = block ? block[1]! : content\n\n const typeMatch = body.match(/type\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n const host = body.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1]\n if (!typeMatch || !host) return []\n\n const engine = schemeToEngine(typeMatch[1]!)\n if (!engine) return []\n\n const port = body.match(/port\\s*:\\s*(\\d+)/)?.[1]\n const database = body.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1] ?? ''\n\n return [\n {\n host,\n port: port ? Number(port) : undefined,\n database,\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n}\n\nexport const typeormParser = { name: 'typeorm', parse }\n","import path from 'node:path'\nimport { exists, readJson } from '../shared.js'\nimport { schemeToEngine, type DbConfig } from './shared.js'\n\ninterface SequelizeConfigEntry {\n dialect?: string\n host?: string\n port?: number\n database?: string\n}\n\ntype SequelizeConfig = Record<string, SequelizeConfigEntry>\n\n// Sequelize stores per-environment configs under config/config.json. We read\n// every named environment so a service that declares production + staging\n// surfaces both DB targets.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const configPath = path.join(serviceDir, 'config', 'config.json')\n if (!(await exists(configPath))) return []\n const raw = await readJson<SequelizeConfig>(configPath)\n\n const out: DbConfig[] = []\n const seen = new Set<string>()\n for (const entry of Object.values(raw)) {\n if (!entry?.dialect || !entry.host) continue\n const engine = schemeToEngine(entry.dialect)\n if (!engine) continue\n const key = `${engine}://${entry.host}:${entry.port ?? ''}/${entry.database ?? ''}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push({\n host: entry.host,\n port: entry.port,\n database: entry.database ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: configPath,\n })\n }\n return out\n}\n\nexport const sequelizeParser = { name: 'sequelize', parse }\n","import path from 'node:path'\nimport { exists, readYaml } from '../shared.js'\nimport { engineFromImage, type DbConfig } from './shared.js'\n\ninterface ComposeService {\n image?: string\n ports?: (string | number)[]\n environment?: Record<string, string> | string[]\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\nfunction portFromService(svc: ComposeService): number | undefined {\n for (const raw of svc.ports ?? []) {\n const str = String(raw)\n // \"5432:5432\", \"5432\", or \"host:5432\" → take the trailing port.\n const last = str.split(':').pop()\n const n = Number(last)\n if (Number.isFinite(n) && n > 0) return n\n }\n return undefined\n}\n\nfunction databaseFromEnv(svc: ComposeService): string {\n const env = svc.environment\n const get = (key: string): string | undefined => {\n if (!env) return undefined\n if (Array.isArray(env)) {\n for (const line of env) {\n const [k, v] = line.split('=')\n if (k === key) return v\n }\n return undefined\n }\n return env[key]\n }\n return get('POSTGRES_DB') ?? get('MYSQL_DATABASE') ?? get('MONGO_INITDB_DATABASE') ?? ''\n}\n\n// Service-local docker-compose.yml — every service whose image we recognise\n// becomes a candidate DB. The compose service name doubles as the host since\n// that's how peer services on the same compose network reach it.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(serviceDir, name)\n if (!(await exists(abs))) continue\n const raw = await readYaml<ComposeFile>(abs)\n if (!raw?.services) return []\n\n const out: DbConfig[] = []\n for (const [serviceName, svc] of Object.entries(raw.services)) {\n if (!svc.image) continue\n const meta = engineFromImage(svc.image)\n if (!meta) continue\n out.push({\n host: serviceName,\n port: portFromService(svc),\n database: databaseFromEnv(svc),\n engine: meta.engine,\n engineVersion: meta.engineVersion,\n sourceFile: abs,\n })\n }\n return out\n }\n return []\n}\n\nexport const dockerComposeParser = { name: 'docker-compose', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ConfigNode, GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n configId,\n confidenceForExtracted,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport {\n IGNORED_DIRS,\n isConfigFile,\n isPythonVenvDir,\n makeEdgeId,\n type DiscoveredService,\n} from './shared.js'\nimport { ensureFileNode, toPosix } from './calls/shared.js'\n\n// Walk a service directory and collect every config file path\n// (yaml/yml + .env-shaped). We deliberately stop at file paths here so nothing\n// in this module reads file contents — .env files routinely carry secrets\n// (ADR-016).\nexport async function walkConfigFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true })\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (entry.isFile() && isConfigFile(entry.name).match) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\n// Phase 3 — turn each config file into a ConfigNode with a CONFIGURED_BY edge\n// from its owning service.\nexport async function addConfigNodes(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n for (const service of services) {\n const configFiles = await walkConfigFiles(service.dir)\n for (const file of configFiles) {\n const relPath = path.relative(scanPath, file)\n const node: ConfigNode = {\n id: configId(relPath),\n type: NodeType.ConfigNode,\n name: path.basename(file),\n path: relPath,\n fileType: isConfigFile(path.basename(file)).fileType,\n }\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n // file-awareness §1 — the config file IS the relationship origin.\n // ensureFileNode creates the FileNode + CONTAINS edge so CONFIGURED_BY\n // lands file-grained rather than service-level.\n const relToService = toPosix(path.relative(service.dir, file))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relToService,\n )\n nodesAdded += fn\n edgesAdded += fe\n const edge: GraphEdge = {\n id: makeEdgeId(fileNodeId, node.id, EdgeType.CONFIGURED_BY),\n source: fileNodeId,\n target: node.id,\n type: EdgeType.CONFIGURED_BY,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: relPath.split(path.sep).join('/') },\n }\n if (!graph.hasEdge(edge.id)) {\n graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport type { GraphEdge, RouteNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n routeId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, snippet, toPosix } from './calls/shared.js'\n\n// Server-route extraction (ADR-119). Reads a mainstream router's route table —\n// Express (`app.get`/`router.post`/…), Fastify (`fastify.get`, `fastify.route`),\n// Next.js (app-router `route.*` handlers, pages `api/` handlers) — and\n// materialises each route as a RouteNode at (method, path-template) grain, owned\n// by its service through a `service ──CONTAINS──▶ route` edge. This is the\n// server half of the static contract-matching in calls/route-match.ts: a\n// client call site is matched to the route it names, bridging the two static\n// islands into a route-grained cross-service CALLS edge.\n//\n// Scope is mainstream routers only, gated by manifest dependency (the extensible\n// registry pattern — coverage grows one router at a time, not by exhaustive\n// heuristics). A service with none of these deps is skipped.\n\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\n// The HTTP verbs an Express / Fastify router registers a route under. `all`\n// registers a method-agnostic route; it normalises to the `ALL` method token.\nconst ROUTER_METHODS = new Set([\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'options',\n 'head',\n 'all',\n])\n\n// The exported handler names a Next.js app-router `route.*` file uses, one per\n// HTTP method it serves.\nconst NEXT_APP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])\n\nconst JS_ROUTE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx'])\n\nexport interface ExtractedRoute {\n method: string // upper-cased HTTP method, or 'ALL' for a method-agnostic route\n pathTemplate: string // canonicalised declared template, e.g. '/users/:id'\n line: number // 1-indexed line the route is declared on\n framework: string // 'express' | 'fastify' | 'next'\n}\n\n// ── path-template canonicalisation ──────────────────────────────────────────\n\n// Canonicalise a declared route path for use as a RouteNode's stable template:\n// drop any query/hash, ensure a leading slash, drop a trailing slash (except\n// root). The template keeps its declared params verbatim (`:id`, `{id}`) so an\n// OBSERVED server span carrying the same `http.route` lands on the same node.\nexport function canonicalizeTemplate(raw: string): string {\n let p = raw.split('?')[0]!.split('#')[0]!\n if (!p.startsWith('/')) p = '/' + p\n if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1)\n return p\n}\n\n// A path segment is dynamic when it names a parameter rather than a literal.\n// Covers the router param syntaxes (`:id`, `{id}`, `[id]`, `[...slug]`), a\n// reconstructed client interpolation (`:param`), and a concrete value a client\n// URL carries in a param position (all-digits, uuid, long hex / Mongo id).\nfunction isDynamicSegment(seg: string): boolean {\n if (seg.length === 0) return false\n if (seg.includes(':')) return true // :id (express/fastify) or reconstructed :param\n if (seg.startsWith('{') || seg.startsWith('[')) return true // {id} openapi, [id] next\n if (/^\\d+$/.test(seg)) return true // concrete numeric id\n if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true // uuid\n if (/^[0-9a-f]{24,}$/i.test(seg)) return true // mongo objectid / long hex token\n return false\n}\n\n// Normalise a path-template to a param-agnostic matching key: every dynamic\n// segment collapses to `:param`, every literal segment lowercases. This is the\n// comparison form both a server route's declared template and a client call's\n// URL path reduce to, so `/users/:id` (server) matches `/users/123` and\n// `/users/${userId}` (client). The declared template is kept intact on the\n// node; only matching uses this reduction.\nexport function normalizePathTemplate(raw: string): string {\n const canonical = canonicalizeTemplate(raw)\n const segments = canonical.split('/').filter((s) => s.length > 0)\n const normalised = segments.map((seg) => (isDynamicSegment(seg) ? ':param' : seg.toLowerCase()))\n return '/' + normalised.join('/')\n}\n\n// ── AST helpers ─────────────────────────────────────────────────────────────\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child, visit)\n }\n}\n\n// The interior text of a string literal, stripped of quotes. Returns null for a\n// template string carrying interpolation (a route path is a static literal).\nfunction staticStringText(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n // Empty string literal ('' — no fragment child).\n return ''\n }\n if (node.type === 'template_string') {\n // Only a template with no substitution is a usable static path.\n for (let i = 0; i < node.namedChildCount; i++) {\n if (node.namedChild(i)?.type === 'template_substitution') return null\n }\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : ''\n }\n return null\n}\n\n// Read a string-valued property off an object-expression node (`{ url: '/x' }`).\nfunction objectStringProp(objNode: Parser.SyntaxNode, key: string): string | null {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n if (!k) continue\n const kText = k.type === 'string' ? staticStringText(k) : k.text\n if (kText !== key) continue\n const v = pair.childForFieldName('value')\n if (v) return staticStringText(v)\n }\n return null\n}\n\n// Read the `method` property off a Fastify route-options object. Accepts a\n// single string (`method: 'GET'`) or an array (`method: ['GET','POST']`).\nfunction fastifyRouteMethods(objNode: Parser.SyntaxNode): string[] {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? staticStringText(k) : k.text) : null\n if (kText !== 'method') continue\n const v = pair.childForFieldName('value')\n if (!v) return []\n if (v.type === 'string' || v.type === 'template_string') {\n const s = staticStringText(v)\n return s ? [s.toUpperCase()] : []\n }\n if (v.type === 'array') {\n const out: string[] = []\n for (let j = 0; j < v.namedChildCount; j++) {\n const el = v.namedChild(j)\n if (el && (el.type === 'string' || el.type === 'template_string')) {\n const s = staticStringText(el)\n if (s) out.push(s.toUpperCase())\n }\n }\n return out\n }\n }\n return []\n}\n\n// ── Express / Fastify call-expression routes ────────────────────────────────\n\n// Recognise route registrations of the shape `<router>.<method>('/path', …)`\n// and Fastify's `<fastify>.route({ method, url })`. The guard that keeps this\n// off `db.get('key')` / `_.get(obj, path)` is a string first argument that\n// starts with '/', combined with the caller-side dep gate in addRoutes (only\n// services that depend on express / fastify reach here). Mount-prefix\n// resolution (`app.use('/api', router)`) and `.route().get()` chaining are out\n// of scope for this slice — the literal declared path is captured as-is.\nexport function serverRoutesFromSource(\n source: string,\n parser: Parser,\n hasExpress: boolean,\n hasFastify: boolean,\n): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n const framework = hasExpress ? 'express' : 'fastify'\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (!fn || fn.type !== 'member_expression') return\n const prop = fn.childForFieldName('property')\n if (!prop) return\n const method = prop.text.toLowerCase()\n const args = node.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first) return\n const line = node.startPosition.row + 1\n\n if (ROUTER_METHODS.has(method)) {\n const p = staticStringText(first)\n if (p && p.startsWith('/')) {\n out.push({\n method: method === 'all' ? 'ALL' : method.toUpperCase(),\n pathTemplate: canonicalizeTemplate(p),\n line,\n framework,\n })\n }\n return\n }\n\n // Fastify's generic form: fastify.route({ method, url }).\n if (method === 'route' && hasFastify && first.type === 'object') {\n const url = objectStringProp(first, 'url')\n if (!url || !url.startsWith('/')) return\n const methods = fastifyRouteMethods(first)\n const list = methods.length > 0 ? methods : ['ALL']\n for (const m of list) {\n out.push({\n method: m === 'ALL' ? 'ALL' : m.toUpperCase(),\n pathTemplate: canonicalizeTemplate(url),\n line,\n framework: 'fastify',\n })\n }\n }\n })\n return out\n}\n\n// ── Next.js file-convention routes ──────────────────────────────────────────\n\nfunction segmentsOf(relFile: string): string[] {\n return toPosix(relFile).split('/').filter((s) => s.length > 0)\n}\n\n// An app-router route handler file: `<…>/app/**/route.{js,ts,jsx,tsx,…}` (also\n// `src/app`). Route handlers live only in a file literally named `route`.\nexport function isNextAppRouteFile(relFile: string): boolean {\n const segs = segmentsOf(relFile)\n if (!segs.includes('app')) return false\n const base = segs[segs.length - 1] ?? ''\n return /^route\\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base)\n}\n\n// A pages-router API file: `<…>/pages/api/**/*.{js,ts,…}`. Skips Next's special\n// `_app` / `_document` / `_middleware` files, which aren't routes.\nexport function isNextPagesApiFile(relFile: string): boolean {\n const segs = segmentsOf(relFile)\n const pagesIdx = segs.indexOf('pages')\n if (pagesIdx === -1 || segs[pagesIdx + 1] !== 'api') return false\n const base = segs[segs.length - 1] ?? ''\n if (/^_(app|document|middleware)\\./.test(base)) return false\n return JS_ROUTE_EXTENSIONS.has(path.extname(base))\n}\n\n// Convert one Next path segment to its template form: route groups `(group)`\n// drop out, `[...slug]` / `[[...slug]]` catch-alls and `[id]` dynamics become\n// `:name`, everything else stays literal.\nfunction nextSegment(seg: string): string | null {\n if (seg.startsWith('(') && seg.endsWith(')')) return null // route group — not in the URL\n const catchAll = seg.match(/^\\[\\[?\\.\\.\\.(.+?)\\]?\\]$/)\n if (catchAll) return ':' + catchAll[1]\n const dynamic = seg.match(/^\\[(.+?)\\]$/)\n if (dynamic) return ':' + dynamic[1]\n return seg\n}\n\n// Derive the URL path-template from an app-router `route.*` file's directory:\n// `app/users/[id]/route.ts` → `/users/:id`.\nfunction nextAppPathTemplate(relFile: string): string {\n const segs = segmentsOf(relFile)\n const appIdx = segs.lastIndexOf('app')\n const between = segs.slice(appIdx + 1, segs.length - 1) // dirs between app/ and route.*\n const parts: string[] = []\n for (const seg of between) {\n const mapped = nextSegment(seg)\n if (mapped !== null) parts.push(mapped)\n }\n return '/' + parts.join('/')\n}\n\n// Derive the URL path-template from a pages `api/` file:\n// `pages/api/users/[id].ts` → `/api/users/:id`, `pages/api/index.ts` → `/api`.\nfunction nextPagesApiPathTemplate(relFile: string): string {\n const segs = segmentsOf(relFile)\n const pagesIdx = segs.indexOf('pages')\n const rest = segs.slice(pagesIdx + 1) // api/...\n const parts: string[] = []\n for (let i = 0; i < rest.length; i++) {\n let seg = rest[i]!\n if (i === rest.length - 1) {\n seg = seg.replace(/\\.(?:js|jsx|mjs|cjs|ts|tsx)$/, '')\n if (seg === 'index') continue\n }\n const mapped = nextSegment(seg)\n if (mapped !== null) parts.push(mapped)\n }\n return '/' + parts.join('/')\n}\n\n// The exported HTTP-method handler names in an app-router `route.*` file:\n// `export async function GET() {}` / `export const POST = …`. Each is one route.\nfunction nextAppMethods(root: Parser.SyntaxNode): { method: string; line: number }[] {\n const out: { method: string; line: number }[] = []\n walk(root, (node) => {\n if (node.type !== 'export_statement') return\n const decl = node.childForFieldName('declaration')\n if (!decl) return\n const line = node.startPosition.row + 1\n if (decl.type === 'function_declaration') {\n const name = decl.childForFieldName('name')?.text\n if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line })\n return\n }\n if (decl.type === 'lexical_declaration' || decl.type === 'variable_declaration') {\n for (let i = 0; i < decl.namedChildCount; i++) {\n const d = decl.namedChild(i)\n if (d?.type !== 'variable_declarator') continue\n const name = d.childForFieldName('name')?.text\n if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line })\n }\n }\n })\n return out\n}\n\nfunction nextRoutesFromFile(\n source: string,\n relFile: string,\n parser: Parser,\n): ExtractedRoute[] {\n if (isNextAppRouteFile(relFile)) {\n const tree = parseSource(parser, source)\n const template = nextAppPathTemplate(relFile)\n return nextAppMethods(tree.rootNode).map(({ method, line }) => ({\n method,\n pathTemplate: canonicalizeTemplate(template),\n line,\n framework: 'next',\n }))\n }\n if (isNextPagesApiFile(relFile)) {\n // A pages API handler is the module's default export and serves every\n // method — recorded as a single method-agnostic route.\n return [\n {\n method: 'ALL',\n pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),\n line: 1,\n framework: 'next',\n },\n ]\n }\n return []\n}\n\n// ── producer ────────────────────────────────────────────────────────────────\n\nexport async function addRoutes(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const deps = {\n ...(service.pkg.dependencies ?? {}),\n ...(service.pkg.devDependencies ?? {}),\n }\n const hasExpress = deps['express'] !== undefined\n const hasFastify = deps['fastify'] !== undefined\n const hasNext = deps['next'] !== undefined\n if (!hasExpress && !hasFastify && !hasNext) continue\n\n const files = await loadSourceFiles(service.dir)\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion. A test that spins up a router isn't\n // the service's declared route surface.\n if (isTestPath(file.path)) continue\n if (!JS_ROUTE_EXTENSIONS.has(path.extname(file.path))) continue\n const relFile = toPosix(path.relative(service.dir, file.path))\n\n let routes: ExtractedRoute[]\n try {\n if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {\n routes = nextRoutesFromFile(file.content, relFile, jsParser)\n } else if (hasExpress || hasFastify) {\n routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify)\n } else {\n routes = []\n }\n } catch (err) {\n recordExtractionError('route extraction', file.path, err)\n continue\n }\n if (routes.length === 0) continue\n\n for (const route of routes) {\n const rid = routeId(service.pkg.name, route.method, route.pathTemplate)\n if (!graph.hasNode(rid)) {\n const node: RouteNode = {\n id: rid,\n type: NodeType.RouteNode,\n name: `${route.method} ${route.pathTemplate}`,\n service: service.pkg.name,\n method: route.method,\n pathTemplate: route.pathTemplate,\n path: relFile,\n line: route.line,\n framework: route.framework,\n discoveredVia: 'static',\n }\n graph.addNode(rid, node)\n nodesAdded++\n }\n // `service ──CONTAINS──▶ route` — the service owns its routes the same\n // way it owns its files (file-awareness.md §2). Structural ownership,\n // evidence pinned to the defining file:line.\n const containsId = extractedEdgeId(service.node.id, rid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: service.node.id,\n target: rid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relFile,\n line: route.line,\n snippet: snippet(file.content, route.line),\n },\n }\n graph.addEdgeWithKey(containsId, service.node.id, rid, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge, GrpcMethodNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n grpcMethodId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { IGNORED_DIRS, isPythonVenvDir, isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { snippet, toPosix } from './calls/shared.js'\n\n// gRPC `.proto` service/method extraction (ADR-123). gRPC used to engage only at\n// service grain — the client-stub detector in calls/grpc.ts maps a\n// `new OrderServiceClient()` to one `infra:grpc-service:*` node, and nothing read\n// the method surface at all. This producer reads the `.proto` service contract\n// as DATA (a bounded line-scan, no tree-sitter grammar — polyglot files are read\n// as data, CLAUDE.md) and materialises each `rpc` as a `GrpcMethodNode`, owned by\n// the service the proto lives in through a `service ──CONTAINS──▶ method` edge.\n//\n// This is the static half of two-sided gRPC observation: the node is keyed on the\n// fully-qualified `<package>.<Service>` name — the exact `rpc.service` an OBSERVED\n// execution span carries — so a declared method and its observed counterpart fuse\n// onto one node into a method-grain divergence (docs/contracts/otel-ingest.md\n// §gRPC methods). Scope is the service/method definitions only; message/field\n// grain, `import` resolution across proto files, and error-detail enrichment are\n// out of scope for this slice.\n\nconst PROTO_EXTENSION = '.proto'\n\n// A `.proto` service/method definition parsed out of one file.\nexport interface ExtractedGrpcMethod {\n rpcService: string // fully-qualified `<package>.<Service>` (or bare `<Service>` when the file declares no package)\n rpcMethod: string // bare method name, e.g. `GetOrder`\n line: number // 1-indexed line the `rpc` is declared on\n}\n\n// ── `.proto` parsing (data, not a grammar) ──────────────────────────────────\n\n// The file-level `package foo.bar;` declaration, if present. gRPC fully-qualifies\n// `rpc.service` as `<package>.<Service>`, so the package prefixes every service\n// name in the file. proto2/proto3 both use this single-line form.\nfunction packageOf(content: string): string | null {\n const m = content.match(/^\\s*package\\s+([A-Za-z_][A-Za-z0-9_.]*)\\s*;/m)\n return m ? m[1]! : null\n}\n\n// The 1-indexed line an absolute character offset falls on.\nfunction lineAt(content: string, offset: number): number {\n return content.slice(0, offset).split('\\n').length\n}\n\n// Scan a `.proto` file's `service X { rpc M(Req) returns (Res); }` blocks and\n// emit one method per `rpc`. Brace-balanced so a service body is read to its\n// close; the `rpc` scan is confined to that body. Streaming qualifiers\n// (`rpc M(stream Req) returns (stream Res)`) don't change the method identity, so\n// they're accepted and ignored. Comment-only or option lines that merely mention\n// `rpc`/`service` don't match — both anchors require the keyword followed by an\n// identifier and the structural token (`{` for a service, `(` for an rpc).\nexport function grpcMethodsFromProto(content: string, fqPackage: string | null): ExtractedGrpcMethod[] {\n const out: ExtractedGrpcMethod[] = []\n const serviceRe = /\\bservice\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{/g\n let sm: RegExpExecArray | null\n while ((sm = serviceRe.exec(content)) !== null) {\n const serviceName = sm[1]!\n const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName\n // Walk from the opening brace to its matching close, tracking depth so a\n // nested block (an inline `option`/message) doesn't end the service early.\n const bodyStart = serviceRe.lastIndex // index just past the `{`\n let depth = 1\n let i = bodyStart\n for (; i < content.length && depth > 0; i++) {\n const ch = content[i]\n if (ch === '{') depth++\n else if (ch === '}') depth--\n }\n const body = content.slice(bodyStart, i - 1)\n const rpcRe = /\\brpc\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/g\n let rm: RegExpExecArray | null\n while ((rm = rpcRe.exec(body)) !== null) {\n const rpcMethod = rm[1]!\n const line = lineAt(content, bodyStart + rm.index)\n out.push({ rpcService, rpcMethod, line })\n }\n // Continue the outer scan past this service body.\n serviceRe.lastIndex = i\n }\n return out\n}\n\n// ── file discovery ──────────────────────────────────────────────────────────\n\n// Walk a service directory for `.proto` files, honouring the shared ignore set\n// (node_modules, .git, …) and Python venvs the same way walkSourceFiles does.\n// `.proto` isn't a SERVICE_FILE_EXTENSION, so the source-file walker skips it —\n// this is a dedicated pass over the proto contract surface.\nasync function walkProtoFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (entry.isFile() && path.extname(entry.name) === PROTO_EXTENSION) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\n// ── producer ─────────────────────────────────────────────────────────────────\n\nexport async function addGrpcMethods(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const protoPaths = await walkProtoFiles(service.dir)\n for (const protoPath of protoPaths) {\n // ADR-065 #1 — test-scope exclusion. A `.proto` under a test tree isn't the\n // service's declared contract surface.\n if (isTestPath(protoPath)) continue\n const relFile = toPosix(path.relative(service.dir, protoPath))\n\n let content: string\n try {\n content = await fs.readFile(protoPath, 'utf8')\n } catch (err) {\n recordExtractionError('proto extraction', protoPath, err)\n continue\n }\n\n let methods: ExtractedGrpcMethod[]\n try {\n methods = grpcMethodsFromProto(content, packageOf(content))\n } catch (err) {\n recordExtractionError('proto extraction', protoPath, err)\n continue\n }\n if (methods.length === 0) continue\n\n for (const method of methods) {\n const mid = grpcMethodId(method.rpcService, method.rpcMethod)\n if (!graph.hasNode(mid)) {\n const node: GrpcMethodNode = {\n id: mid,\n type: NodeType.GrpcMethodNode,\n name: `${method.rpcService}/${method.rpcMethod}`,\n rpcService: method.rpcService,\n rpcMethod: method.rpcMethod,\n path: relFile,\n line: method.line,\n discoveredVia: 'static',\n }\n graph.addNode(mid, node)\n nodesAdded++\n }\n // `service ──CONTAINS──▶ method` — the service owns the methods its\n // `.proto` declares, the same structural verb it has over its routes\n // (ADR-119) and files (file-awareness.md §2). Evidence pinned to the\n // `rpc` line. The node id is the wire-canonical FQN, so an OBSERVED\n // execution span lands on this same node — declared and observed fuse.\n const containsId = extractedEdgeId(service.node.id, mid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: service.node.id,\n target: mid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relFile,\n line: method.line,\n snippet: snippet(content, method.line),\n },\n }\n graph.addEdgeWithKey(containsId, service.node.id, mid, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { GraphEdge, InfraNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n} from '@neat.is/types'\nimport { noteExtractedDropped } from '../errors.js'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n isTestPath,\n makeEdgeId,\n maskCommentsInSource,\n type DiscoveredService,\n} from '../shared.js'\nimport { addHttpCallEdges } from './http.js'\nimport { addRouteCallEdges } from './route-match.js'\nimport { ensureFileNode, loadSourceFiles, toPosix, type ExternalEndpoint } from './shared.js'\nimport { kafkaEndpointsFromFile } from './kafka.js'\nimport { redisEndpointsFromFile } from './redis.js'\nimport { awsEndpointsFromFile } from './aws.js'\nimport { grpcEndpointsFromFile } from './grpc.js'\nimport { supabaseEndpointsFromFile } from './supabase.js'\n\nexport interface CallExtractResult {\n nodesAdded: number\n edgesAdded: number\n}\n\nfunction edgeTypeFromEndpoint(ep: ExternalEndpoint): (typeof EdgeType)[keyof typeof EdgeType] {\n switch (ep.edgeType) {\n case 'PUBLISHES_TO':\n return EdgeType.PUBLISHES_TO\n case 'CONSUMES_FROM':\n return EdgeType.CONSUMES_FROM\n default:\n return EdgeType.CALLS\n }\n}\n\nfunction isAwsKind(kind: string): boolean {\n return (\n kind.startsWith('aws-') ||\n kind.startsWith('s3') ||\n kind.startsWith('dynamodb')\n )\n}\n\nasync function addExternalEndpointEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<CallExtractResult> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n const endpoints: ExternalEndpoint[] = []\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion. Tests stay registered as\n // service-internal (via the file walk earlier); only outbound\n // endpoint inference from them is filtered.\n if (isTestPath(file.path)) continue\n // ADR-065 #2 — comment-body exclusion. The regex-based extractors\n // (redis / kafka / aws / grpc) scan raw file.content; URLs inside\n // JSDoc / line / block comments leaked through to the graph in the\n // v0.3.0 medusa run. Mask comments while preserving line/column for\n // evidence line-mapping.\n const masked = maskCommentsInSource(file.content)\n const maskedFile = { path: file.path, content: masked }\n endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir))\n }\n if (endpoints.length === 0) continue\n\n const seenEdges = new Set<string>()\n for (const ep of endpoints) {\n if (!graph.hasNode(ep.infraId)) {\n const node: InfraNode = {\n id: ep.infraId,\n type: NodeType.InfraNode,\n name: ep.name,\n // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,\n // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the\n // bucket / table kinds from aws.ts.\n provider: isAwsKind(ep.kind) ? 'aws' : 'self',\n kind: ep.kind,\n }\n graph.addNode(node.id, node)\n nodesAdded++\n }\n\n const edgeType = edgeTypeFromEndpoint(ep)\n const confidence = confidenceForExtracted(ep.confidenceKind)\n // File-first (file-awareness.md §1): the endpoint relationship originates\n // from the file the call site lives in, with the owning service\n // ──CONTAINS──▶ file edge alongside it (§2). File-node existence is\n // independent of edge-target precision (ADR-089 amendment) — a matched\n // call site is a parsed fact, so the FileNode + CONTAINS materialize\n // regardless of how confident we are about the resolved target.\n const relFile = toPosix(ep.evidence.file)\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n // Precision floor (ADR-066 §3). Only the file→target edge is gated:\n // sub-threshold candidates are recorded as drops (banner accounting) and\n // never added to the graph; the file and its call site still surface.\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: ep.infraId,\n type: edgeType,\n confidence,\n confidenceKind: ep.confidenceKind,\n evidence: ep.evidence,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, ep.infraId, edgeType)\n if (seenEdges.has(edgeId)) continue\n seenEdges.add(edgeId)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: ep.infraId,\n type: edgeType,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ep.evidence,\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n\nexport async function addCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<CallExtractResult> {\n const http = await addHttpCallEdges(graph, services)\n const ext = await addExternalEndpointEdges(graph, services)\n // Cross-service contract matching (ADR-119). Runs after the RouteNodes are in\n // the graph (addRoutes, a prior phase) so client call sites can be matched\n // against the full route table, minting route-grained CALLS edges.\n const routes = await addRouteCallEdges(graph, services)\n return {\n nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,\n edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded,\n }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport Python from 'tree-sitter-python'\nimport type { GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n isTestPath,\n makeEdgeId,\n urlMatchesHost,\n type DiscoveredService,\n} from '../shared.js'\nimport { recordExtractionError, noteExtractedDropped } from '../errors.js'\nimport {\n buildServiceHostIndex,\n ensureFileNode,\n loadSourceFiles,\n snippet,\n toPosix,\n} from './shared.js'\n\n// JS uses `string_fragment` for the textual interior of a template/string;\n// Python uses `string_content` inside a `string` node. Either way we want the\n// raw textual content (no quotes), so we accept both.\nconst STRING_LITERAL_NODE_TYPES = new Set(['string_fragment', 'string_content'])\n\n// ADR-065 #3 — JSX external-link exclusion. Tags whose URL-attr strings are\n// user-clickable hyperlinks, not service-to-service calls.\nconst JSX_EXTERNAL_LINK_TAGS = new Set(['a', 'Link', 'NavLink', 'ExternalLink', 'Anchor'])\n\n// Walk upward from a string-literal node to detect whether it sits inside a\n// JSX attribute on an external-link element. Returns true if the literal\n// should be filtered.\nfunction isInsideJsxExternalLink(node: Parser.SyntaxNode): boolean {\n let cursor: Parser.SyntaxNode | null = node.parent\n // Step out of the string wrapper if needed (parent is `string` /\n // `template_string`).\n while (cursor) {\n if (cursor.type === 'jsx_attribute') {\n // The element that owns this attribute. jsx_attribute lives inside\n // jsx_opening_element / jsx_self_closing_element.\n let owner: Parser.SyntaxNode | null = cursor.parent\n while (owner && owner.type !== 'jsx_opening_element' && owner.type !== 'jsx_self_closing_element') {\n owner = owner.parent\n }\n if (!owner) return false\n // First named child of an opening/self-closing element is the tag name\n // (`identifier` or `member_expression`).\n const tagNode = owner.namedChild(0)\n const tagName = tagNode?.text ?? ''\n // For `<Foo.Bar>` we just want the rightmost ident; pick after the\n // last dot.\n const right = tagName.includes('.') ? tagName.split('.').pop()! : tagName\n return JSX_EXTERNAL_LINK_TAGS.has(right)\n }\n cursor = cursor.parent\n }\n return false\n}\n\n// Collect (literal text, ast-node) pairs so the JSX-context check has the\n// node available. Comment tokens have no string_fragment / string_content\n// children in tree-sitter — JSDoc text lives inside `comment` nodes — so\n// comment-body exclusion comes for free with this AST walk (ADR-065 #2).\nfunction collectStringLiterals(\n node: Parser.SyntaxNode,\n out: { text: string; node: Parser.SyntaxNode }[],\n): void {\n if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node })\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectStringLiterals(child, out)\n }\n}\n\n// A matched outbound call: the host the URL literal names and the 1-indexed\n// line it sits on. File-first extraction (file-awareness.md §1) originates a\n// CALLS edge from the file the call site lives in, so the position travels with\n// the host rather than being recovered after the fact.\nexport interface HttpCallSite {\n host: string\n line: number\n}\n\n// tree-sitter's node binding copies a string handed to `parser.parse` into a\n// fixed scratch buffer of ~32K code units and throws a bare \"Invalid argument\"\n// once the source is larger — it never names the size as the cause. NEAT's own\n// `cli.ts`, `ingest.ts`, and `installers/javascript.ts` all clear 40K, so the\n// http-call extractor was quietly skipping the three most interesting files in\n// the repo while dogfooding NEAT-on-NEAT. The callback form sidesteps the\n// buffer entirely: tree-sitter pulls the text in chunks we keep well under the\n// limit, so a file of any length parses. Chunking is by `.length` (UTF-16 code\n// units), which is exactly what the buffer counts.\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nexport function callsFromSource(\n source: string,\n parser: Parser,\n knownHosts: Set<string>,\n): HttpCallSite[] {\n const tree = parseSource(parser, source)\n const literals: { text: string; node: Parser.SyntaxNode }[] = []\n collectStringLiterals(tree.rootNode, literals)\n const out: HttpCallSite[] = []\n for (const lit of literals) {\n // ADR-065 #3 — JSX external-link exclusion. URL strings on <a>, <Link>,\n // <NavLink>, <ExternalLink>, <Anchor> are user-clickable hyperlinks, not\n // service calls.\n if (isInsideJsxExternalLink(lit.node)) continue\n for (const host of knownHosts) {\n // ADR-065 #5 — exact hostname match (not substring containment).\n // `medusa.cloud` no longer matches `@medusajs/medusa`.\n if (urlMatchesHost(lit.text, host)) {\n out.push({ host, line: lit.node.startPosition.row + 1 })\n }\n }\n }\n return out\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\n// HTTP CALLS via URL hostname match. Parser is picked per file extension:\n// .py uses tree-sitter-python; everything else uses tree-sitter-javascript.\n//\n// File-first (file-awareness.md §1): each matched call site originates a\n// `file:<svc>:<relPath> ──CALLS──▶ target` edge plus the owning service's\n// CONTAINS edge, rather than collapsing every call in a service to one\n// service-level edge.\n//\n// File-node existence is independent of edge-target precision (file-awareness.md\n// §1, ADR-089 amendment). A matched call site is a parsed fact — the file and\n// its `service ──CONTAINS──▶ file` edge are certain regardless of how confident\n// we are about *what* it calls. So the FileNode + CONTAINS materialize for every\n// matched site; only the file→target CALLS edge is subject to the precision\n// floor. A scheme-qualified URL literal to a registered service grades at the\n// floor (url-literal-service-target, 0.7) so the declared HTTP dependency enters\n// the graph; if the floor is raised past it for diagnostics the file and its\n// call site still surface, without claiming the resolved target.\nexport async function addHttpCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const pyParser = makePyParser()\n\n // Host → owning ServiceNode id (ADR-065 #5), shared with the route-matching\n // producer so both resolve a URL's host to a service the same way.\n const { knownHosts, hostToNodeId } = buildServiceHostIndex(services)\n\n let nodesAdded = 0\n let edgesAdded = 0\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n // File grain: one file→target CALLS per (file, target) pair, even when a\n // file names the same host on several lines (function-level is deferred).\n const seen = new Set<string>()\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion.\n if (isTestPath(file.path)) continue\n const parser = path.extname(file.path) === '.py' ? pyParser : jsParser\n let sites: HttpCallSite[]\n try {\n sites = callsFromSource(file.content, parser, knownHosts)\n } catch (err) {\n recordExtractionError('http call extraction', file.path, err)\n continue\n }\n if (sites.length === 0) continue\n const relFile = toPosix(path.relative(service.dir, file.path))\n for (const site of sites) {\n const targetId = hostToNodeId.get(site.host)\n if (!targetId || targetId === service.node.id) continue\n const dedupKey = `${relFile}|${targetId}`\n if (seen.has(dedupKey)) continue\n seen.add(dedupKey)\n // A scheme-qualified URL literal that resolves to a registered service\n // is a declared HTTP dependency (static-extraction contract §5). It\n // grades at the precision floor rather than below it, so the EXTRACTED\n // CALLS edge enters the graph and missing-observed can flag a declared-\n // but-never-driven upstream (issue #592). Still below structural /\n // verified-call-site: no call expression wraps the literal.\n const confidence = confidenceForExtracted('url-literal-service-target')\n const ev = {\n file: relFile,\n line: site.line,\n snippet: snippet(file.content, site.line),\n }\n // The matched call site is a parsed fact: materialize the FileNode and\n // its `service ──CONTAINS──▶ file` edge regardless of target precision\n // (file-awareness.md §1, ADR-089 amendment). The file surfaces even\n // when the resolved target sits below the floor.\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n // The file→target CALLS edge alone is subject to the precision floor.\n // A sub-floor target is recorded as a drop (banner accounting) and not\n // claimed as a resolved edge — the file and its call site still stand.\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: targetId,\n type: EdgeType.CALLS,\n confidence,\n confidenceKind: 'url-literal-service-target',\n evidence: ev,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, targetId, EdgeType.CALLS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: targetId,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ev,\n }\n graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge)\n edgesAdded++\n }\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport type { GraphEdge, RouteNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n serviceId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { isTestPath, makeEdgeId, urlMatchesHost, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError, noteExtractedDropped } from '../errors.js'\nimport { normalizePathTemplate } from '../routes.js'\nimport {\n buildServiceHostIndex,\n ensureFileNode,\n loadSourceFiles,\n snippet,\n toPosix,\n} from './shared.js'\n\n// Cross-service contract matching (ADR-119). This is the bridge between the two\n// static islands: a client call site names a URL (host + method + path); a\n// server RouteNode (extracted by routes.ts) declares (method, path-template).\n// When a client call's (host→service, method, normalised path) resolves to a\n// server route, this producer mints a route-grained EXTRACTED CALLS edge from\n// the client's FileNode to the server's RouteNode. It reuses the host→service\n// resolution the HTTP producers share (buildServiceHostIndex / urlMatchesHost),\n// adding path-template matching for the route half.\n//\n// The edge pairs with the OBSERVED server-span edge landing on the same\n// RouteNode (#576), giving get_divergences a file-precise, two-sided comparison\n// at route grain instead of only at service grain.\n//\n// Mainstream clients only: `fetch`, `axios` (default instance + method calls),\n// and node `http`/`https` `.request`/`.get`. The host and path must sit in the\n// same URL literal (or template literal) for a match — split base-URL + path\n// across variables is out of scope for this slice.\n\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nconst JS_CLIENT_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx'])\nconst AXIOS_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request'])\n\nexport interface ClientCallSite {\n host: string // the known host the URL literal named (basename or pkg name)\n method?: string // upper-cased; undefined when not statically determinable\n pathTemplate: string // the URL path, with `:param` for interpolations\n line: number\n snippet: string\n}\n\n// ── AST helpers ─────────────────────────────────────────────────────────────\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child, visit)\n }\n}\n\n// Reconstruct the URL text a string / template-string argument names. A template\n// substitution (`${id}`) becomes the literal `:param` so the reconstructed URL\n// is a valid string with a param-shaped path segment: `/users/${id}` →\n// `/users/:param`. Returns null for anything that isn't a string-ish literal.\nfunction reconstructUrl(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n return ''\n }\n if (node.type === 'template_string') {\n let out = ''\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_fragment') out += child.text\n else if (child.type === 'template_substitution') out += ':param'\n }\n // A template with no fragments/subs (empty) — fall back to stripped text.\n if (out.length === 0) {\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : ''\n }\n return out\n }\n return null\n}\n\n// Read the `method` string off an options / config object (`{ method: 'POST' }`).\nfunction methodFromOptions(objNode: Parser.SyntaxNode): string | undefined {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? stringText(k) : k.text) : null\n if (kText !== 'method') continue\n const v = pair.childForFieldName('value')\n if (v && (v.type === 'string' || v.type === 'template_string')) {\n const s = stringText(v)\n return s ? s.toUpperCase() : undefined\n }\n }\n return undefined\n}\n\nfunction stringText(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n return ''\n }\n return null\n}\n\n// The URL string a config object names (`axios({ url: '…', method })`).\nfunction urlNodeFromConfig(objNode: Parser.SyntaxNode): Parser.SyntaxNode | null {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? stringText(k) : k.text) : null\n if (kText === 'url') return pair.childForFieldName('value')\n }\n return null\n}\n\n// Parse the path out of a reconstructed URL string. Returns null when the string\n// isn't URL-shaped (no scheme + host). `:param` in the path survives parsing.\nfunction pathOf(urlStr: string): string | null {\n try {\n const candidate = urlStr.startsWith('//') ? `http:${urlStr}` : urlStr\n const parsed = new URL(candidate)\n return parsed.pathname || '/'\n } catch {\n return null\n }\n}\n\n// Resolve which known host a reconstructed URL names (ADR-065 #5 — scheme +\n// exact hostname). Returns the host token or null.\nfunction matchHost(urlStr: string, knownHosts: Set<string>): string | null {\n for (const host of knownHosts) {\n if (urlMatchesHost(urlStr, host)) return host\n }\n return null\n}\n\n// ── client call-site recognition ────────────────────────────────────────────\n\n// Extract every recognised HTTP client call site whose URL literal names a\n// known host. Each site carries the method (when statically determinable) and\n// the path-template, ready to be matched against the server route table.\nexport function clientCallSitesFromSource(\n source: string,\n parser: Parser,\n knownHosts: Set<string>,\n): ClientCallSite[] {\n const tree = parseSource(parser, source)\n const out: ClientCallSite[] = []\n\n const push = (\n urlNode: Parser.SyntaxNode,\n method: string | undefined,\n callNode: Parser.SyntaxNode,\n ): void => {\n const urlStr = reconstructUrl(urlNode)\n if (!urlStr) return\n const host = matchHost(urlStr, knownHosts)\n if (!host) return\n const p = pathOf(urlStr)\n if (p === null) return\n const line = callNode.startPosition.row + 1\n out.push({\n host,\n method,\n pathTemplate: p,\n line,\n snippet: snippet(source, line),\n })\n }\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (!fn) return\n const args = node.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first) return\n\n // fetch(url, opts?) — global or member (globalThis.fetch).\n const fnName =\n fn.type === 'identifier'\n ? fn.text\n : fn.type === 'member_expression'\n ? (fn.childForFieldName('property')?.text ?? '')\n : ''\n\n if (fn.type === 'identifier' && fnName === 'fetch') {\n const opts = args?.namedChild(1)\n const method = opts && opts.type === 'object' ? (methodFromOptions(opts) ?? 'GET') : 'GET'\n push(first, method, node)\n return\n }\n\n // axios(url | config) — default instance called directly.\n if (fn.type === 'identifier' && fnName === 'axios') {\n if (first.type === 'object') {\n const urlNode = urlNodeFromConfig(first)\n if (urlNode) push(urlNode, methodFromOptions(first) ?? 'GET', node)\n } else {\n const opts = args?.namedChild(1)\n const method = opts && opts.type === 'object' ? (methodFromOptions(opts) ?? 'GET') : 'GET'\n push(first, method, node)\n }\n return\n }\n\n if (fn.type === 'member_expression') {\n const obj = fn.childForFieldName('object')\n const objName = obj?.text ?? ''\n\n // axios.get('/x') / axios.post('/x', body) / axios.request({ url, method }).\n if (objName === 'axios' && AXIOS_METHODS.has(fnName)) {\n if (fnName === 'request' && first.type === 'object') {\n const urlNode = urlNodeFromConfig(first)\n if (urlNode) push(urlNode, methodFromOptions(first) ?? 'GET', node)\n } else {\n push(first, fnName.toUpperCase(), node)\n }\n return\n }\n\n // node http/https .request(url, …) / .get(url, …).\n if ((objName === 'http' || objName === 'https') && (fnName === 'request' || fnName === 'get')) {\n const opts = args?.namedChild(1)\n const method =\n opts && opts.type === 'object'\n ? (methodFromOptions(opts) ?? (fnName === 'get' ? 'GET' : 'GET'))\n : 'GET'\n push(first, method, node)\n return\n }\n }\n })\n\n return out\n}\n\n// ── route index + matching ──────────────────────────────────────────────────\n\ninterface RouteEntry {\n method: string // upper, or 'ALL'\n normalizedPath: string\n routeNodeId: string\n}\n\n// Group every RouteNode in the graph by its owning ServiceNode id, keyed for\n// (method, normalised-path) lookup. Built once per pass so client matching is a\n// map read, not a graph scan per call site.\nfunction buildRouteIndex(graph: NeatGraph): Map<string, RouteEntry[]> {\n const index = new Map<string, RouteEntry[]>()\n graph.forEachNode((_id, attrs) => {\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n const owner = serviceId(route.service)\n const entry: RouteEntry = {\n method: route.method.toUpperCase(),\n normalizedPath: normalizePathTemplate(route.pathTemplate),\n routeNodeId: route.id,\n }\n const list = index.get(owner)\n if (list) list.push(entry)\n else index.set(owner, [entry])\n })\n return index\n}\n\n// A client call matches a route when the normalised paths agree and the methods\n// are compatible: exact, or the route is method-agnostic (`ALL`), or the client\n// method couldn't be read statically.\nfunction findRoute(\n entries: RouteEntry[],\n method: string | undefined,\n normalizedPath: string,\n): RouteEntry | undefined {\n return entries.find(\n (e) =>\n e.normalizedPath === normalizedPath &&\n (e.method === 'ALL' || method === undefined || e.method === method),\n )\n}\n\nexport async function addRouteCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const { knownHosts, hostToNodeId } = buildServiceHostIndex(services)\n const routeIndex = buildRouteIndex(graph)\n if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 }\n\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n // One edge per (client file, route) pair even if a file calls the route on\n // several lines (function grain is deferred, matching http.ts).\n const seen = new Set<string>()\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion.\n if (isTestPath(file.path)) continue\n if (!JS_CLIENT_EXTENSIONS.has(path.extname(file.path))) continue\n\n let sites: ClientCallSite[]\n try {\n sites = clientCallSitesFromSource(file.content, jsParser, knownHosts)\n } catch (err) {\n recordExtractionError('route-match call extraction', file.path, err)\n continue\n }\n if (sites.length === 0) continue\n\n const relFile = toPosix(path.relative(service.dir, file.path))\n for (const site of sites) {\n const serverServiceId = hostToNodeId.get(site.host)\n // Skip an unresolved host or a self-call (intra-service — no\n // cross-service contract to match, mirroring http.ts).\n if (!serverServiceId || serverServiceId === service.node.id) continue\n const entries = routeIndex.get(serverServiceId)\n if (!entries) continue\n const normalizedPath = normalizePathTemplate(site.pathTemplate)\n const match = findRoute(entries, site.method, normalizedPath)\n if (!match) continue\n\n const dedupKey = `${relFile}|${match.routeNodeId}`\n if (seen.has(dedupKey)) continue\n seen.add(dedupKey)\n\n // The matched call site is a parsed fact — the client FileNode and its\n // service ──CONTAINS──▶ file edge materialise regardless (file-awareness\n // §1). Only the file→route edge is gated by the precision floor.\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n\n // A matched client↔route contract grades at verified-call-site (0.85):\n // both endpoints are recognised — a framework-aware client shape and a\n // parsed route definition — so it clears the floor and enters the graph\n // (ADR-119).\n const confidence = confidenceForExtracted('verified-call-site')\n const ev = {\n file: relFile,\n line: site.line,\n snippet: site.snippet,\n method: site.method ?? match.method,\n pathTemplate: site.pathTemplate,\n }\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: match.routeNodeId,\n type: EdgeType.CALLS,\n confidence,\n confidenceKind: 'verified-call-site',\n evidence: ev,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, match.routeNodeId, EdgeType.CALLS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: match.routeNodeId,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ev,\n }\n graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Match `producer.send({ topic: \"orders\" ... })` and `producer.send({\n// topic: 'orders' })` plus the two-arg form `producer.send(\"orders\", ...)`.\n// Kafka client libraries vary; these two forms cover kafkajs + node-rdkafka\n// well enough for static extraction. Same shape covers consumer.subscribe.\nconst PRODUCER_TOPIC_RE =\n /(?:producer|kafkaProducer)[\\s\\S]{0,40}?\\.send\\s*\\(\\s*\\{[\\s\\S]{0,200}?topic\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\nconst CONSUMER_TOPIC_RE =\n /(?:consumer|kafkaConsumer)[\\s\\S]{0,40}?\\.(?:subscribe|run)\\s*\\(\\s*\\{[\\s\\S]{0,200}?topic[s]?\\s*:\\s*(?:\\[\\s*)?['\"`]([^'\"`]+)['\"`]/g\n\nfunction findAll(re: RegExp, text: string): { topic: string; index: number }[] {\n re.lastIndex = 0\n const out: { topic: string; index: number }[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n out.push({ topic: m[1]!, index: m.index })\n }\n return out\n}\n\nexport function kafkaEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const make = (topic: string, edgeType: 'PUBLISHES_TO' | 'CONSUMES_FROM'): void => {\n const key = `${edgeType}|${topic}`\n if (seen.has(key)) return\n seen.add(key)\n const line = lineOf(file.content, topic)\n out.push({\n infraId: infraId('kafka-topic', topic),\n name: topic,\n kind: 'kafka-topic',\n edgeType,\n // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —\n // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site\n // tier (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, 'PUBLISHES_TO')\n for (const { topic } of findAll(CONSUMER_TOPIC_RE, file.content)) make(topic, 'CONSUMES_FROM')\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Redis URLs in source — `redis://host[:port]` or `rediss://...`. We only\n// catch literal strings; env-driven URLs go through the database parsers\n// (.env, ormconfig, etc.) and don't need a CALLS edge.\nconst REDIS_URL_RE = /redis(?:s)?:\\/\\/(?:[^@'\"`\\s]+@)?([^:/'\"`\\s]+)(?::(\\d+))?/g\n\nexport function redisEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n REDIS_URL_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = REDIS_URL_RE.exec(file.content)) !== null) {\n const host = m[1]!\n if (seen.has(host)) continue\n seen.add(host)\n const line = lineOf(file.content, host)\n out.push({\n infraId: infraId('redis', host),\n name: host,\n kind: 'redis',\n edgeType: 'CALLS',\n // `redis://host` URL literal — the scheme is structural support, but no\n // call expression is verified to wire it through. URL-with-structural-\n // support tier (ADR-066).\n confidenceKind: 'url-with-structural-support',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// AWS SDK v3 calls. We catch S3 (`Bucket: \"x\"` near a `S3Client`-using\n// PutObjectCommand / GetObjectCommand / DeleteObjectCommand) and DynamoDB\n// (`TableName: \"x\"` near GetCommand / PutCommand / DynamoDBClient). The\n// pattern is intentionally permissive: a literal Bucket/TableName near an\n// SDK constant is good enough evidence; misses are fine because non-static\n// resources can't be catalogued anyway.\nconst S3_BUCKET_RE = /Bucket\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\nconst DYNAMO_TABLE_RE = /TableName\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\n\nfunction hasMarker(text: string, markers: string[]): boolean {\n return markers.some((m) => text.includes(m))\n}\n\nfunction findAll(re: RegExp, text: string): { name: string; index: number }[] {\n re.lastIndex = 0\n const out: { name: string; index: number }[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n out.push({ name: m[1]!, index: m.index })\n }\n return out\n}\n\nexport function awsEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const make = (kind: string, name: string): void => {\n const key = `${kind}|${name}`\n if (seen.has(key)) return\n seen.add(key)\n const line = lineOf(file.content, name)\n out.push({\n infraId: infraId(kind, name),\n name,\n kind,\n edgeType: 'CALLS',\n // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName\n // literal — framework-aware recognizer, verified-call-site tier\n // (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n if (hasMarker(file.content, ['S3Client', 'PutObjectCommand', 'GetObjectCommand', 'DeleteObjectCommand'])) {\n for (const { name } of findAll(S3_BUCKET_RE, file.content)) make('s3-bucket', name)\n }\n if (\n hasMarker(file.content, [\n 'DynamoDBClient',\n 'DynamoDBDocumentClient',\n 'GetCommand',\n 'PutCommand',\n 'QueryCommand',\n 'UpdateCommand',\n 'DeleteCommand',\n ])\n ) {\n for (const { name } of findAll(DYNAMO_TABLE_RE, file.content)) make('dynamodb-table', name)\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Client-construction in JS/TS:\n//\n// const client = new orders_proto.OrderService(...)\n// const client = new OrdersClient('orders.internal:50051', ...)\n// const client = new S3Client({ region: 'us-east-1' })\n//\n// The same `new <Name>Client(...)` shape is used by gRPC client stubs, AWS\n// SDK v3 service clients, and a long tail of other SDKs. v0.3.0 mapped every\n// `*Client(...)` to `infra:grpc-service:*`, which was true for the demo and\n// false for every AWS service medusa imported.\n//\n// Per ADR-065 / #238, classification is import-aware:\n// 1. file imports `@aws-sdk/client-<suffix>` and `<Name>` lowercases to the\n// same alphanumeric tail (`S3Client` ↔ `client-s3`,\n// `CognitoIdentityProviderClient` ↔ `client-cognito-identity-provider`)\n// → kind `aws-<suffix>` (e.g. `infra:aws-s3:S3`).\n// 2. file imports `@grpc/grpc-js` or any `*_grpc_pb` generated stub\n// → kind `grpc-service` (the legitimate gRPC path; demo unchanged).\n// 3. otherwise → kind `service` (the safe default — accurate but\n// uninformative, the v0.3.0 grpc lie is removed).\nconst GRPC_CLIENT_RE = /new\\s+([A-Z][A-Za-z0-9_]*)Client\\s*\\(\\s*['\"`]?([^,'\"`)]+)?/g\nconst AWS_SDK_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@aws-sdk\\/client-([a-z0-9-]+)['\"`]/g\nconst GRPC_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@grpc\\/grpc-js['\"`]|_grpc_pb['\"`]/\n\nfunction isLikelyAddress(value: string | undefined): boolean {\n if (!value) return false\n // Reject code expressions: object literals, anything with whitespace.\n // process.env.X contains dots and would otherwise pass the dot check.\n if (/\\s/.test(value) || value.startsWith('{')) return false\n return /:\\d{2,5}$/.test(value) || value.includes('.')\n}\n\nfunction normaliseForMatch(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]/g, '')\n}\n\ninterface ImportContext {\n awsSdkSuffixes: Map<string, string> // normalised → raw suffix (e.g. 's3' → 's3')\n hasGrpcImport: boolean\n}\n\nfunction readImports(content: string): ImportContext {\n const awsSdkSuffixes = new Map<string, string>()\n AWS_SDK_IMPORT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = AWS_SDK_IMPORT_RE.exec(content)) !== null) {\n const raw = m[1]!\n awsSdkSuffixes.set(normaliseForMatch(raw), raw)\n }\n return {\n awsSdkSuffixes,\n hasGrpcImport: GRPC_IMPORT_RE.test(content),\n }\n}\n\nfunction classifyClient(\n symbol: string,\n ctx: ImportContext,\n): { kind: string } | null {\n const key = normaliseForMatch(symbol)\n const awsRaw = ctx.awsSdkSuffixes.get(key)\n if (awsRaw) return { kind: `aws-${awsRaw}` }\n if (ctx.hasGrpcImport) return { kind: 'grpc-service' }\n // ADR-065 #5-adjacent — a bare `new <Name>Client()` with no AWS or gRPC\n // import context is ambiguous; could be an in-process client object\n // (`new QueryClient()` from @tanstack/react-query, `new PrismaClient()`,\n // a domain Client class, etc.). Refuse to emit an edge rather than guess\n // a `service` kind that produces false positives. v0.3.3 medusa run had\n // a QueryClient false positive before this guard.\n return null\n}\n\nexport function grpcEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const ctx = readImports(file.content)\n GRPC_CLIENT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = GRPC_CLIENT_RE.exec(file.content)) !== null) {\n const symbol = m[1]!\n const addr = m[2]?.trim()\n const name = isLikelyAddress(addr) ? addr! : symbol\n if (seen.has(name)) continue\n const classified = classifyClient(symbol, ctx)\n if (!classified) continue\n seen.add(name)\n const { kind } = classified\n const line = lineOf(file.content, m[0])\n out.push({\n infraId: infraId(kind, name),\n name,\n kind,\n edgeType: 'CALLS',\n // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import\n // context — import-aware classification per #238. Verified-call-site\n // tier (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Supabase clients in JS/TS:\n//\n// import { createClient } from '@supabase/supabase-js'\n// const supabase = createClient(process.env.SUPABASE_URL, key)\n// const db = createClient('https://abc.supabase.co', key)\n// supabase.from('orders').select()\n// db.auth.getUser()\n//\n// import { createServerClient } from '@supabase/ssr'\n// const supabase = createServerClient(url, key, { cookies })\n//\n// Supabase apps talk to *.supabase.co exclusively through these client\n// constructors. The HTTP-call extractor never sees the host: it lives behind\n// `createClient`, and in the common case the URL is `process.env.SUPABASE_URL`\n// (or `EXPO_PUBLIC_SUPABASE_URL`), not a literal. So a production-observed\n// `service → *.supabase.co` CALLS edge arrives with no declared twin and shows\n// up as a false `missing-extracted` divergence. This extractor closes that gap.\n//\n// Classification is import-aware, the same discipline grpc.ts / aws.ts use\n// (#238): a bare `createClient(...)` is too common a name to claim without the\n// `@supabase/*` import in scope. The constructor names map to the package that\n// owns them:\n// - `createClient` ← @supabase/supabase-js\n// - `createServerClient` / `createBrowserClient` ← @supabase/ssr\nconst SUPABASE_JS_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@supabase\\/supabase-js['\"`]/\nconst SUPABASE_SSR_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@supabase\\/ssr['\"`]/\n\n// First argument of a Supabase client constructor. Group 1 is the constructor\n// name, group 2 (when present) is a string-literal URL. A non-literal first arg\n// (`process.env.SUPABASE_URL`, a config getter) leaves group 2 undefined — we\n// still match the construction, we just can't resolve the literal host.\nconst SUPABASE_CLIENT_RE =\n /\\b(createClient|createServerClient|createBrowserClient)\\s*\\(\\s*(?:['\"`]([^'\"`]*)['\"`])?/g\n\n// A *.supabase.co host pulled out of a literal URL argument. We only treat the\n// constructor's URL as a resolvable host when the literal actually names a\n// supabase.co domain — anything else (a self-hosted Supabase behind a custom\n// domain, a placeholder) stays the unresolved env target rather than a guessed\n// host. Reading the literal is the only honest host source (file-awareness §6).\nfunction hostFromLiteral(literal: string | undefined): string | null {\n if (!literal) return null\n const m = /^https?:\\/\\/([^/:'\"`\\s]+)/.exec(literal.trim())\n if (!m) return null\n const host = m[1]!\n if (!/\\.supabase\\.(co|in)$/i.test(host)) return null\n return host\n}\n\ninterface ImportContext {\n hasSupabaseJs: boolean\n hasSupabaseSsr: boolean\n}\n\nfunction readImports(content: string): ImportContext {\n return {\n hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),\n hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content),\n }\n}\n\n// A constructor name is only a Supabase client when the package that owns it is\n// imported in the same file. `createClient` needs @supabase/supabase-js;\n// `createServerClient` / `createBrowserClient` need @supabase/ssr. Without the\n// import in scope we refuse to emit — a bare `createClient` could be anything.\nfunction constructorMatchesImport(name: string, ctx: ImportContext): boolean {\n if (name === 'createClient') return ctx.hasSupabaseJs\n return ctx.hasSupabaseSsr\n}\n\nexport function supabaseEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const ctx = readImports(file.content)\n if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return []\n\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n SUPABASE_CLIENT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {\n const ctor = m[1]!\n if (!constructorMatchesImport(ctor, ctx)) continue\n\n // When the first argument is a literal *.supabase.co URL we resolve the\n // real host. Otherwise the URL is env-driven (`process.env.SUPABASE_URL`,\n // a config getter) and unknowable statically — the edge still has to land,\n // so it resolves to a stable `supabase:env` target. That target is honest:\n // we read the @supabase import and the `createClient` call, but not a host,\n // so we don't fabricate one (file-awareness §6).\n const host = hostFromLiteral(m[2])\n const name = host ?? 'env'\n if (seen.has(name)) continue\n seen.add(name)\n\n const line = lineOf(file.content, m[0])\n out.push({\n infraId: infraId('supabase', name),\n name,\n kind: 'supabase',\n edgeType: 'CALLS',\n // `createClient(...)` from @supabase/supabase-js (or createServerClient /\n // createBrowserClient from @supabase/ssr) with the import in scope — a\n // framework-aware recognizer matched the SDK shape. Verified-call-site\n // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, makeEdgeId, readYaml, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { classifyImage, makeInfraNode } from './shared.js'\n\ninterface ComposeService {\n image?: string\n build?: string | { context?: string }\n depends_on?: string[] | Record<string, unknown>\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\nfunction dependsOnList(value: ComposeService['depends_on']): string[] {\n if (!value) return []\n if (Array.isArray(value)) return value\n return Object.keys(value)\n}\n\nfunction serviceNameToServiceNode(\n name: string,\n services: DiscoveredService[],\n): string | null {\n for (const s of services) {\n if (s.node.name === name || path.basename(s.dir) === name) return s.node.id\n }\n return null\n}\n\n// Project-level docker-compose.yml describes deployment topology. Each compose\n// service that is *not* one of the discovered ServiceNodes becomes an\n// InfraNode (databases, brokers, caches). depends_on lists become DEPENDS_ON\n// edges from the dependent to its dependency, regardless of whether the\n// endpoint is a ServiceNode or InfraNode — the edge itself is the deployment\n// fact, not the role.\nexport async function addComposeInfra(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n let composePath: string | null = null\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(scanPath, name)\n if (await exists(abs)) {\n composePath = abs\n break\n }\n }\n if (!composePath) return { nodesAdded, edgesAdded }\n\n let compose: ComposeFile\n try {\n compose = await readYaml<ComposeFile>(composePath)\n } catch (err) {\n recordExtractionError(\n 'infra docker-compose',\n path.relative(scanPath, composePath),\n err,\n )\n return { nodesAdded, edgesAdded }\n }\n if (!compose?.services) return { nodesAdded, edgesAdded }\n const evidenceFile = path.relative(scanPath, composePath).split(path.sep).join('/')\n\n const composeNameToNodeId = new Map<string, string>()\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const matchedServiceId = serviceNameToServiceNode(composeName, services)\n if (matchedServiceId) {\n composeNameToNodeId.set(composeName, matchedServiceId)\n continue\n }\n const kind = svc.image ? classifyImage(svc.image) : 'container'\n const node = makeInfraNode(kind, composeName)\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n composeNameToNodeId.set(composeName, node.id)\n }\n\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const sourceId = composeNameToNodeId.get(composeName)\n if (!sourceId) continue\n for (const dep of dependsOnList(svc.depends_on)) {\n const targetId = composeNameToNodeId.get(dep)\n if (!targetId) continue\n const edgeId = makeEdgeId(sourceId, targetId, EdgeType.DEPENDS_ON)\n if (graph.hasEdge(edgeId)) continue\n // depends_on declaration from docker-compose.yml — structural deployment\n // fact, structural tier per ADR-066.\n const edge: GraphEdge = {\n id: edgeId,\n source: sourceId,\n target: targetId,\n type: EdgeType.DEPENDS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { InfraNode } from '@neat.is/types'\nimport { NodeType, infraId } from '@neat.is/types'\n\n// ADR-010 reserves the `infra:` prefix; the kind segment lets traversal and\n// MCP tools sub-type without inventing a new top-level NodeType per source.\nexport function makeInfraNode(\n kind: string,\n name: string,\n provider = 'self',\n extras?: { region?: string },\n): InfraNode {\n return {\n id: infraId(kind, name),\n type: NodeType.InfraNode,\n name,\n provider,\n kind,\n ...(extras?.region ? { region: extras.region } : {}),\n }\n}\n\n// Stable kind for an image string like \"postgres:15-alpine\" or \"mysql:8\".\n// The image name itself ends up in the InfraNode `name` field; this function\n// only classifies what the image *is*, so callers can group similar runtimes.\nexport function classifyImage(image: string): string {\n const lower = image.toLowerCase()\n const repo = lower.split(':')[0]!\n const last = repo.split('/').pop() ?? repo\n if (last.startsWith('postgres')) return 'postgres'\n if (last.startsWith('mysql') || last.startsWith('mariadb')) return 'mysql'\n if (last.startsWith('mongo')) return 'mongodb'\n if (last.startsWith('redis')) return 'redis'\n if (last.startsWith('rabbitmq')) return 'rabbitmq'\n if (last.startsWith('kafka') || last.includes('kafka')) return 'kafka'\n if (last.startsWith('memcached')) return 'memcached'\n return 'container'\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, makeEdgeId, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { makeInfraNode } from './shared.js'\nimport { ensureFileNode, toPosix } from '../calls/shared.js'\n\ninterface DockerfileFacts {\n image: string | null\n // Ports declared with `EXPOSE`, in declaration order, deduped. Each one is a\n // network endpoint the container listens on — the structural answer to \"what\n // is this service reachable at\".\n ports: number[]\n // The trimmed `ENTRYPOINT` / `CMD` line that names the process the container\n // runs. Carried as edge evidence so the entrypoint is queryable instead of\n // being a silent fact only the Dockerfile knows.\n entrypoint: string | null\n}\n\n// Read the runtime-shaping instructions out of a Dockerfile in one pass:\n//\n// - FROM → the runtime image. Multi-stage builds report the *runtime* image\n// (the last FROM that isn't `scratch` / a stage alias).\n// - EXPOSE → the ports the container listens on (`EXPOSE 8080 9090`,\n// `EXPOSE 8080/tcp`).\n// - CMD / ENTRYPOINT → the process the container runs. ENTRYPOINT wins when\n// both are present, matching Docker's precedence.\nfunction readDockerfile(content: string): DockerfileFacts {\n let image: string | null = null\n const ports: number[] = []\n let cmd: string | null = null\n let entrypoint: string | null = null\n for (const raw of content.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n if (/^from\\s+/i.test(line)) {\n const candidate = line.split(/\\s+/)[1]\n if (candidate && candidate.toLowerCase() !== 'scratch') image = candidate\n } else if (/^expose\\s+/i.test(line)) {\n for (const token of line.split(/\\s+/).slice(1)) {\n const port = Number.parseInt(token.split('/')[0]!, 10)\n if (Number.isInteger(port) && !ports.includes(port)) ports.push(port)\n }\n } else if (/^entrypoint\\s+/i.test(line)) {\n entrypoint = line\n } else if (/^cmd\\s+/i.test(line)) {\n cmd = line\n }\n }\n return { image, ports, entrypoint: entrypoint ?? cmd }\n}\n\n// For each ServiceNode that has a Dockerfile in its dir, emit a\n// `infra:container-image:<image>` InfraNode and a RUNS_ON edge from the\n// service to the image.\nexport async function addDockerfileRuntimes(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const dockerfilePath = path.join(service.dir, 'Dockerfile')\n if (!(await exists(dockerfilePath))) continue\n let content: string\n try {\n content = await fs.readFile(dockerfilePath, 'utf8')\n } catch (err) {\n recordExtractionError(\n 'infra dockerfile',\n path.relative(scanPath, dockerfilePath),\n err,\n )\n continue\n }\n const facts = readDockerfile(content)\n if (!facts.image) continue\n\n const node = makeInfraNode('container-image', facts.image)\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n\n // file-awareness §1 — the Dockerfile IS the file that declares the runtime;\n // anchor the infra edges on a FileNode for it, not on the service. The file\n // node is service-scoped, so two services that both `FROM node:20` keep\n // distinct entrypoints and exposed ports instead of colliding on the shared\n // image node.\n const relDockerfile = toPosix(path.relative(service.dir, dockerfilePath))\n const evidenceFile = toPosix(path.relative(scanPath, dockerfilePath))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relDockerfile,\n )\n nodesAdded += fn\n edgesAdded += fe\n\n // RUNS_ON carries the runtime image, and — when the Dockerfile declares one\n // — the entrypoint/CMD line as evidence.snippet, so the process the\n // container runs is queryable instead of a silent fact.\n const edgeId = makeEdgeId(fileNodeId, node.id, EdgeType.RUNS_ON)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: node.id,\n type: EdgeType.RUNS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: evidenceFile,\n ...(facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}),\n },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n\n // Each EXPOSE port becomes an `infra:port:<n>` node the Dockerfile\n // CONNECTS_TO — the structural answer to \"what is this service reachable\n // at\". Without this, a declared listener is a fact only the Dockerfile\n // knows; with it, the port is a first-class node the topology can reach.\n for (const port of facts.ports) {\n const portNode = makeInfraNode('port', String(port))\n if (!graph.hasNode(portNode.id)) {\n graph.addNode(portNode.id, portNode)\n nodesAdded++\n }\n const portEdgeId = makeEdgeId(fileNodeId, portNode.id, EdgeType.CONNECTS_TO)\n if (graph.hasEdge(portEdgeId)) continue\n const portEdge: GraphEdge = {\n id: portEdgeId,\n source: fileNodeId,\n target: portNode.id,\n type: EdgeType.CONNECTS_TO,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` },\n }\n graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge)\n edgesAdded++\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { IGNORED_DIRS, isPythonVenvDir, makeEdgeId } from '../shared.js'\nimport { toPosix } from '../calls/shared.js'\nimport { makeInfraNode } from './shared.js'\n\n// Light pass: catalogue `resource \"aws_*\" \"name\"` blocks in any *.tf file and\n// wire the references between them. We don't run a real Terraform backend, but\n// the body of every resource block names the other resources it consumes\n// (`aws_db_instance.main.endpoint`, `${aws_security_group.db.id}`, …). Those\n// `<type>.<name>` references are the structural fact that turns an edgeless\n// catalogue into a topology: a resource nothing points at is declared-but-unused,\n// a resource something points at is in use. Without this, an RDS instance the\n// app server depends on looks identical to a forgotten S3 bucket.\nconst RESOURCE_RE = /resource\\s+\"(aws_[A-Za-z0-9_]+)\"\\s+\"([A-Za-z0-9_-]+)\"/g\n// A reference to another declared resource. The negative look-behind keeps us\n// from matching the tail of a longer identifier (`my_aws_thing.x`).\nconst REFERENCE_RE = /(?<![\\w.])(aws_[A-Za-z0-9_]+)\\.([A-Za-z0-9_-]+)/g\n\ninterface TfResource {\n type: string\n name: string\n nodeId: string\n body: string\n bodyOffset: number\n}\n\nasync function walkTfFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name) || entry.name === '.terraform') continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkTfFiles(child, depth + 1, max)))\n } else if (entry.isFile() && entry.name.endsWith('.tf')) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\n// Find the index just past the `}` that closes the block whose opening `{`\n// sits at or after `from`. Returns the body span (exclusive of the braces).\n// Balanced-brace scan — good enough for HCL, which doesn't put bare braces in\n// string literals often enough to matter for a reference catalogue.\nfunction blockBody(content: string, from: number): { body: string; offset: number } | null {\n const open = content.indexOf('{', from)\n if (open === -1) return null\n let depth = 0\n for (let i = open; i < content.length; i++) {\n const ch = content[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return { body: content.slice(open + 1, i), offset: open + 1 }\n }\n }\n return null\n}\n\nfunction lineAt(content: string, index: number): number {\n let line = 1\n for (let i = 0; i < index && i < content.length; i++) {\n if (content[i] === '\\n') line++\n }\n return line\n}\n\nexport async function addTerraformResources(\n graph: NeatGraph,\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n const files = await walkTfFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n const evidenceFile = toPosix(path.relative(scanPath, file))\n\n // First pass: register every resource as a node and remember its body so\n // the second pass can resolve references against the set of declared names.\n const resources: TfResource[] = []\n const byKey = new Map<string, TfResource>()\n RESOURCE_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = RESOURCE_RE.exec(content)) !== null) {\n const type = m[1]!\n const name = m[2]!\n const node = makeInfraNode(type, name, 'aws')\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n const span = blockBody(content, RESOURCE_RE.lastIndex)\n const resource: TfResource = {\n type,\n name,\n nodeId: node.id,\n body: span?.body ?? '',\n bodyOffset: span?.offset ?? RESOURCE_RE.lastIndex,\n }\n resources.push(resource)\n byKey.set(`${type}.${name}`, resource)\n }\n\n // Second pass: a `<type>.<name>` reference inside a resource body that\n // names another declared resource becomes a DEPENDS_ON edge from the\n // referencing resource to the one it consumes. Structural tier (ADR-066) —\n // the HCL literally says it depends on it.\n for (const resource of resources) {\n const seen = new Set<string>()\n REFERENCE_RE.lastIndex = 0\n let ref: RegExpExecArray | null\n while ((ref = REFERENCE_RE.exec(resource.body)) !== null) {\n const key = `${ref[1]!}.${ref[2]!}`\n if (key === `${resource.type}.${resource.name}`) continue\n const target = byKey.get(key)\n if (!target) continue\n if (seen.has(target.nodeId)) continue\n seen.add(target.nodeId)\n const edgeId = makeEdgeId(resource.nodeId, target.nodeId, EdgeType.DEPENDS_ON)\n if (graph.hasEdge(edgeId)) continue\n const line = lineAt(content, resource.bodyOffset + ref.index)\n const edge: GraphEdge = {\n id: edgeId,\n source: resource.nodeId,\n target: target.nodeId,\n type: EdgeType.DEPENDS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, line, snippet: key },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parseAllDocuments } from 'yaml'\nimport type { NeatGraph } from '../../graph.js'\nimport { CONFIG_FILE_EXTENSIONS, IGNORED_DIRS, isPythonVenvDir } from '../shared.js'\nimport { makeInfraNode } from './shared.js'\n\ninterface K8sDoc {\n kind?: string\n metadata?: { name?: string; namespace?: string }\n}\n\nconst K8S_KIND_TO_INFRA_KIND: Record<string, string> = {\n Service: 'k8s-service',\n Deployment: 'k8s-deployment',\n StatefulSet: 'k8s-statefulset',\n DaemonSet: 'k8s-daemonset',\n CronJob: 'k8s-cronjob',\n Job: 'k8s-job',\n Ingress: 'k8s-ingress',\n}\n\nasync function walkYamlFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkYamlFiles(child, depth + 1, max)))\n } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path.extname(entry.name))) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\n// Multi-document YAML with kind/metadata.name. We keep the matching simple:\n// any file whose first doc looks k8s-shaped. The match is on `kind` only —\n// random YAML configs (db-config.yaml, etc.) are usually flat objects with no\n// `kind` field, so they're ignored without false positives.\nexport async function addK8sResources(\n graph: NeatGraph,\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n const files = await walkYamlFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n let docs: K8sDoc[]\n try {\n docs = parseAllDocuments(content).map((d) => d.toJSON() as K8sDoc)\n } catch {\n continue\n }\n for (const doc of docs) {\n if (!doc?.kind || !doc.metadata?.name) continue\n const infraKind = K8S_KIND_TO_INFRA_KIND[doc.kind]\n if (!infraKind) continue\n const namespaced = doc.metadata.namespace\n ? `${doc.metadata.namespace}/${doc.metadata.name}`\n : doc.metadata.name\n const node = makeInfraNode(infraKind, namespaced, 'kubernetes')\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded: 0 }\n}\n","import type { NeatGraph } from '../../graph.js'\nimport { type DiscoveredService } from '../shared.js'\nimport { addComposeInfra } from './docker-compose.js'\nimport { addDockerfileRuntimes } from './dockerfile.js'\nimport { addTerraformResources } from './terraform.js'\nimport { addK8sResources } from './k8s.js'\n\nexport interface InfraExtractResult {\n nodesAdded: number\n edgesAdded: number\n}\n\n// Phase 5 — infrastructure. Runs after services so RUNS_ON edges have a\n// ServiceNode to anchor on. Each sub-source contributes its own nodes/edges\n// independently; nothing here mutates ServiceNodes themselves.\nexport async function addInfra(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<InfraExtractResult> {\n const compose = await addComposeInfra(graph, scanPath, services)\n const dockerfile = await addDockerfileRuntimes(graph, services, scanPath)\n const terraform = await addTerraformResources(graph, scanPath)\n const k8s = await addK8sResources(graph, scanPath)\n\n return {\n nodesAdded:\n compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,\n edgesAdded:\n compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded,\n }\n}\n","// Static-extraction pipeline. Phase order is load-bearing:\n// services → aliases → databases (+ compat) → configs → routes → grpc → calls → infra → frontier promotion.\n// Routes precede calls so the cross-service matcher (ADR-119) sees the full\n// RouteNode table when it resolves client call sites to server routes. gRPC\n// `.proto` extraction (ADR-123) mints the method nodes an OBSERVED span fuses onto.\n//\n// Contract anchors (see /docs/contracts.md):\n// * Rule 1 — Every emitted edge carries Provenance.EXTRACTED from @neat.is/types.\n// * Rule 2 — EXTRACTED edges use the plain `${type}:src->tgt` id pattern.\n// Never write under the OBSERVED id pattern; that's ingest.ts's territory.\n// * Rule 5 — Nodes/edges constructed against schemas in @neat.is/types; no\n// local interface redefinitions in this tree.\n// * Rule 8 — No demo-name hardcoding. Driver names come from package.json\n// dependencies; engine names from compat.json via compatPairs().\n// * Rule 14 — ConfigNodes record file existence only; never the contents.\nimport type { NeatGraph } from '../graph.js'\nimport { DEFAULT_PROJECT } from '../graph.js'\nimport { promoteFrontierNodes } from '../ingest.js'\nimport { ensureCompatLoaded } from '../compat.js'\nimport { emitNeatEvent } from '../events.js'\nimport { addServiceNodes, discoverServices } from './services.js'\nimport { addServiceAliases } from './aliases.js'\nimport { addFiles } from './files.js'\nimport { addImports } from './imports.js'\nimport { addDatabasesAndCompat } from './databases/index.js'\nimport { addConfigNodes } from './configs.js'\nimport { addRoutes } from './routes.js'\nimport { addGrpcMethods } from './proto.js'\nimport { addCallEdges } from './calls/index.js'\nimport { addInfra } from './infra/index.js'\nimport {\n drainExtractionErrors,\n writeExtractionErrors,\n drainDroppedExtracted,\n isRejectedLogEnabled,\n writeRejectedExtracted,\n type ExtractionError,\n type DroppedExtractedEdge,\n} from './errors.js'\nimport path from 'node:path'\nimport { retireExtractedEdgesByMissingFile } from './retire.js'\n\nexport interface ExtractResult {\n nodesAdded: number\n edgesAdded: number\n frontiersPromoted: number\n // ADR-065 — per-file extraction failures collected during the pass.\n // `extractionErrors` is the count; `errorEntries` is the drained list,\n // available for callers that want to surface per-file context. Both are\n // present on every pass (zero is observable as a positive signal).\n extractionErrors: number\n errorEntries: ExtractionError[]\n // #140 — count of EXTRACTED edges retired this pass because their\n // evidence.file no longer exists on disk. Zero on a clean pass; non-zero\n // means the snapshot was carrying ghosts from deleted source.\n ghostsRetired: number\n // ADR-066 — count of EXTRACTED candidates dropped at emit time because\n // their graded confidence fell below NEAT_EXTRACTED_PRECISION_FLOOR.\n // Always reported (zero is observable). Detail entries surface in\n // `droppedEntries` and route to rejected.ndjson when\n // NEAT_EXTRACTED_REJECTED_LOG=1.\n extractedDropped: number\n droppedEntries: DroppedExtractedEdge[]\n}\n\nexport interface ExtractOptions {\n // Post-extract policy trigger (ADR-043). Awaited after frontier promotion\n // so policies see the final post-pass graph state. Daemons wire this to\n // evaluateAllPolicies + PolicyViolationsLog.append.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n // Project tag for the extraction-complete event (ADR-051). Defaults to\n // DEFAULT_PROJECT when omitted.\n project?: string\n // ADR-065 — when set, drained extraction errors are appended to this\n // path as ndjson with `source: 'extract'`. Daemons / `neat init` /\n // `neat watch` wire this to `<projectDir>/neat-out/errors.ndjson`. When\n // omitted, errors are still drained and returned in the result, just\n // not persisted.\n errorsPath?: string\n}\n\nexport async function extractFromDirectory(\n graph: NeatGraph,\n scanPath: string,\n opts: ExtractOptions = {},\n): Promise<ExtractResult> {\n await ensureCompatLoaded()\n // Clear any stale entries from a prior pass (the producer-side sink is\n // process-local). Per ADR-065, every pass collects its own errors; we drain\n // again at the end to capture this pass's failures.\n drainExtractionErrors()\n\n const services = await discoverServices(scanPath)\n\n const phase1Nodes = addServiceNodes(graph, services)\n await addServiceAliases(graph, scanPath, services)\n const fileEnum = await addFiles(graph, services)\n const importGraph = await addImports(graph, services)\n const phase2 = await addDatabasesAndCompat(graph, services, scanPath)\n const phase3 = await addConfigNodes(graph, services, scanPath)\n // Route extraction (ADR-119) runs before calls so the RouteNodes exist when\n // addCallEdges' cross-service matcher (route-match.ts) looks them up.\n const routePhase = await addRoutes(graph, services)\n // gRPC `.proto` service/method extraction (ADR-123). Independent of the call\n // phase — it reads the proto contract surface, not JS/TS call sites — and mints\n // the same method nodes an OBSERVED gRPC span lands on, so declared and observed\n // methods fuse.\n const grpcPhase = await addGrpcMethods(graph, services)\n const phase4 = await addCallEdges(graph, services)\n const phase5 = await addInfra(graph, scanPath, services)\n // #140 — drop EXTRACTED edges whose evidence.file no longer exists on disk.\n // Catches the deleted-file ghost case for the full-pass entry point\n // (init / daemon bootstrap). The edited-file case is handled per-mtime by\n // watch.ts's `retireEdgesByFile`. Service dirs are passed alongside scanPath\n // because CALLS-family producers store service-dir-relative paths while\n // configs / databases / infra store scanPath-relative.\n const ghostsRetired = retireExtractedEdgesByMissingFile(\n graph,\n scanPath,\n services.map((s) => s.dir),\n )\n const frontiersPromoted = promoteFrontierNodes(graph)\n\n // Post-extract policy trigger (ADR-043). Fires after frontier promotion so\n // policies see the post-pass graph (including any FRONTIER → OBSERVED edge\n // upgrades that just landed).\n if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph)\n\n // ADR-065 — drain the per-file extraction errors collected during the pass.\n // If a sidecar path was supplied, append the entries; otherwise return them\n // for the caller to surface.\n const errorEntries = drainExtractionErrors()\n if (opts.errorsPath && errorEntries.length > 0) {\n try {\n await writeExtractionErrors(errorEntries, opts.errorsPath)\n } catch (err) {\n console.warn(\n `[neat] failed to write extraction errors to ${opts.errorsPath}: ${(err as Error).message}`,\n )\n }\n }\n\n // ADR-066 — drain the precision-floor drops. Always returned; only\n // persisted when NEAT_EXTRACTED_REJECTED_LOG=1 (opt-in to keep the\n // default sidecar surface quiet).\n const droppedEntries = drainDroppedExtracted()\n if (\n isRejectedLogEnabled() &&\n opts.errorsPath &&\n droppedEntries.length > 0\n ) {\n // rejected.ndjson lives alongside errors.ndjson under neat-out/. Derive\n // from errorsPath rather than re-plumbing a new option.\n const rejectedPath = path.join(path.dirname(opts.errorsPath), 'rejected.ndjson')\n try {\n await writeRejectedExtracted(droppedEntries, rejectedPath)\n } catch (err) {\n console.warn(\n `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${(err as Error).message}`,\n )\n }\n }\n\n const result: ExtractResult = {\n nodesAdded:\n phase1Nodes +\n fileEnum.nodesAdded +\n importGraph.nodesAdded +\n phase2.nodesAdded +\n phase3.nodesAdded +\n routePhase.nodesAdded +\n grpcPhase.nodesAdded +\n phase4.nodesAdded +\n phase5.nodesAdded,\n edgesAdded:\n fileEnum.edgesAdded +\n importGraph.edgesAdded +\n phase2.edgesAdded +\n phase3.edgesAdded +\n routePhase.edgesAdded +\n grpcPhase.edgesAdded +\n phase4.edgesAdded +\n phase5.edgesAdded,\n frontiersPromoted,\n extractionErrors: errorEntries.length,\n errorEntries,\n ghostsRetired,\n extractedDropped: droppedEntries.length,\n droppedEntries,\n }\n\n // extraction-complete (ADR-051). fileCount is the number of services\n // discovered — the closest proxy we have for \"how much source did this\n // pass touch\" without a per-phase file accountant.\n emitNeatEvent({\n type: 'extraction-complete',\n project: opts.project ?? DEFAULT_PROJECT,\n payload: {\n project: opts.project ?? DEFAULT_PROJECT,\n fileCount: services.length,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n },\n })\n\n return result\n}\n","import { existsSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\nimport { NodeType, Provenance } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\n\n// Drop any FileNode left with no edges. A FileNode exists to originate a\n// relationship (file-awareness.md §1); once its CALLS / CONTAINS edges are\n// retired and no OBSERVED traffic remains, the bare node carries nothing and\n// goes too. Called after edge retirement so the snapshot stays consistent with\n// what's on disk. Returns the count dropped.\nfunction dropOrphanedFileNodes(graph: NeatGraph): number {\n const orphans: string[] = []\n graph.forEachNode((id, attrs) => {\n if ((attrs as GraphNode).type !== NodeType.FileNode) return\n if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {\n orphans.push(id)\n }\n })\n for (const id of orphans) graph.dropNode(id)\n return orphans.length\n}\n\n// Drop every EXTRACTED edge whose evidence.file matches the given path, then\n// sweep any FileNode the retirement left orphaned. Called from watch.ts before\n// re-running an extract phase, so the producer's idempotent re-write recreates\n// only the edges that still apply. Edges from the deleted code stay deleted.\n// See docs/contracts/static-extraction.md §Ghost-edge cleanup. Mutation\n// authority lives under extract/* per ADR-030, so the dropEdge call must happen\n// here, not in watch.ts. The returned count is edges dropped (FileNode cleanup\n// is a structural side effect, not a ghost-edge count).\nexport function retireEdgesByFile(graph: NeatGraph, file: string): number {\n const normalized = file.split('\\\\').join('/')\n const toDrop: string[] = []\n graph.forEachEdge((id, attrs) => {\n const edge = attrs as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) return\n if (!edge.evidence?.file) return\n if (edge.evidence.file === normalized) toDrop.push(id)\n })\n for (const id of toDrop) graph.dropEdge(id)\n dropOrphanedFileNodes(graph)\n return toDrop.length\n}\n\n// #140 — full-pass cleanup. Walk every EXTRACTED edge in the graph; if its\n// `evidence.file` cannot be resolved on disk against the scan root or any\n// discovered service directory, drop it. extractFromDirectory calls this at\n// the end of every pass so a daemon bootstrap (or a re-init after the\n// operator deleted some source) gets a snapshot consistent with what's\n// actually on disk.\n//\n// Handles the deleted-file half of the ghost-edge bug. The edited-file half\n// (file still exists, producer no longer emits the edge) is handled by\n// watch.ts's per-file `retireEdgesByFile` on the mtime trigger.\n//\n// Path resolution is tolerant: producers in this tree are inconsistent about\n// whether `evidence.file` is scanPath-relative (configs, databases, infra)\n// or service-dir-relative (calls/*). We try every candidate base before\n// concluding the file is gone — the cost is one extra `existsSync` per\n// service dir per ghost candidate, which is cheap.\nexport function retireExtractedEdgesByMissingFile(\n graph: NeatGraph,\n scanPath: string,\n serviceDirs: readonly string[] = [],\n): number {\n const toDrop: string[] = []\n const bases = [scanPath, ...serviceDirs]\n graph.forEachEdge((id, attrs) => {\n const edge = attrs as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) return\n const evidenceFile = edge.evidence?.file\n if (!evidenceFile) return\n if (path.isAbsolute(evidenceFile)) {\n if (!existsSync(evidenceFile)) toDrop.push(id)\n return\n }\n // Tolerant: the file is \"present\" if any base resolves it.\n const found = bases.some((base) => existsSync(path.join(base, evidenceFile)))\n if (!found) toDrop.push(id)\n })\n for (const id of toDrop) graph.dropEdge(id)\n dropOrphanedFileNodes(graph)\n return toDrop.length\n}\n","// computeDivergences — the thesis surface, derived (ADR-060).\n//\n// Walks the live graph and surfaces the five locked divergence shapes:\n// missing-observed, missing-extracted, version-mismatch, host-mismatch,\n// and compat-violation. Pure: no I/O, no mutation, no async. The function\n// operates on a NeatGraph reference and returns a fresh DivergenceResult\n// each call — there is no persistence (binding rule 2).\n//\n// Mutation authority (ADR-030 / contract #3) is locked to ingest.ts and\n// extract/*; this module reads only. The contract test\n// `packages/core/test/audits/contracts.test.ts` enforces it.\n\nimport type {\n CompatRuleRef,\n Divergence,\n DivergenceResult,\n DivergenceType,\n EdgeTypeValue,\n GraphEdge,\n GraphNode,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n databaseId,\n DivergenceResultSchema,\n EdgeType,\n NodeType,\n parseEdgeId,\n Provenance,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n compatPairs,\n deprecatedApis,\n} from './compat.js'\nimport { confidenceForEdge } from './traverse.js'\n\nexport interface DivergenceQueryOpts {\n // Filter the result to a subset of divergence types. Undefined keeps all\n // five. Empty set returns nothing.\n type?: ReadonlySet<DivergenceType>\n // Drop divergences below this confidence threshold. Undefined keeps all.\n minConfidence?: number\n // Scope to divergences that involve this node (as source or target).\n node?: string\n}\n\n// (source, target, type) → which provenance variants are present. Each\n// bucket is the unit the missing-observed / missing-extracted detectors\n// operate over.\ninterface EdgeBucket {\n source: string\n target: string\n type: GraphEdge['type']\n extracted?: GraphEdge\n observed?: GraphEdge\n inferred?: GraphEdge\n stale?: GraphEdge\n}\n\nfunction bucketKey(source: string, target: string, type: string): string {\n return `${type}|${source}|${target}`\n}\n\nfunction bucketEdges(graph: NeatGraph): Map<string, EdgeBucket> {\n const buckets = new Map<string, EdgeBucket>()\n graph.forEachEdge((id, attrs) => {\n const e = attrs as GraphEdge\n const parsed = parseEdgeId(id)\n // parseEdgeId can fall through to EXTRACTED for unknown shapes — fall\n // back to the edge's own provenance when the id doesn't parse cleanly.\n const provenance = parsed?.provenance ?? e.provenance\n const key = bucketKey(e.source, e.target, e.type)\n const cur =\n buckets.get(key) ?? { source: e.source, target: e.target, type: e.type }\n switch (provenance) {\n case Provenance.EXTRACTED:\n cur.extracted = e\n break\n case Provenance.OBSERVED:\n cur.observed = e\n break\n case Provenance.INFERRED:\n cur.inferred = e\n break\n default:\n // STALE rides on what used to be an OBSERVED edge — the id format\n // stays OBSERVED per identity.ts, so this branch is mostly defensive.\n if (e.provenance === Provenance.STALE) cur.stale = e\n }\n buckets.set(key, cur)\n })\n return buckets\n}\n\nfunction nodeIsFrontier(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.FrontierNode\n}\n\n// A WebSocketChannelNode is minted OBSERVED-only from the HTTP upgrade span\n// (ADR-125): a channel is known from observation, never from static extraction,\n// so it has no declared twin to diverge against. Its edge is a `CONNECTS_TO`,\n// which is in the OBSERVABLE_EDGE_TYPES allowlist, so an OBSERVED-only\n// `service ──CONNECTS_TO──▶ ws-channel` would otherwise flag a spurious\n// `missing-extracted`. Suppressing it where the target is a channel node is\n// signal-preserving — there is no static edge that \"should\" exist — not\n// signal-hiding. See docs/contracts/divergence-query.md.\nfunction nodeIsWebsocketChannel(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.WebSocketChannelNode\n}\n\nfunction clampConfidence(n: number): number {\n if (!Number.isFinite(n)) return 0\n return Math.max(0, Math.min(1, n))\n}\n\nfunction reasonForMissingObserved(source: string, target: string, type: string): string {\n return `Code declares ${source} → ${target} (${type}) but no production traffic has been observed for this edge.`\n}\n\nfunction reasonForMissingExtracted(source: string, target: string, type: string): string {\n return `Production observed ${source} → ${target} (${type}) but static analysis did not surface this edge.`\n}\n\nconst RECOMMENDATION_MISSING_OBSERVED =\n 'Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.'\nconst RECOMMENDATION_MISSING_EXTRACTED =\n 'Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.'\nconst RECOMMENDATION_HOST_MISMATCH =\n 'Check environment-specific config overrides — the runtime host differs from what static configuration declares.'\n\n// ADR-066 §4 — reweight against graded confidence.\n//\n// `missing-extracted` (OBSERVED-led) cascades from the OBSERVED edge's\n// graded confidence (signal-block grade per ADR-066 §2). `missing-observed`\n// weights by the EXTRACTED edge's graded confidence (per-extractor grade\n// per ADR-066 §1). Sub-floor EXTRACTED candidates never enter the graph\n// (precision floor, §3) so what surfaces here is backed by structural or\n// verified-call-site evidence.\n//\n// Falls back to confidenceForEdge for legacy edges loaded from a pre-v0.3.4\n// snapshot that don't carry a stored `confidence` field.\nfunction gradedConfidence(edge: GraphEdge): number {\n if (typeof edge.confidence === 'number') return clampConfidence(edge.confidence)\n return clampConfidence(confidenceForEdge(edge))\n}\n\n// Edge types production can actually emit as OBSERVED traffic — the CALLS\n// family plus cross-service and connection edges. `missing-observed` only\n// makes sense for these: an EXTRACTED edge of one of these types with no\n// OBSERVED twin is a real \"code declares it, production never ran it\" finding.\n//\n// Structural / static-only edge types (IMPORTS, CONFIGURED_BY, CONTAINS,\n// DEPENDS_ON, RUNS_ON) have no runtime span behind them, so there is never an\n// OBSERVED twin to be \"missing\" — measuring their tiers would report every\n// import and every config wire as a missing-observed divergence, which is\n// noise, not signal. Keep this an allowlist (not a denylist) so a new\n// structural edge type stays out of the missing-observed surface by default\n// until it is deliberately added here (divergence-query.md — the five locked\n// types compare CALLS-family edges at the shared grain).\nconst OBSERVABLE_EDGE_TYPES: ReadonlySet<EdgeTypeValue> = new Set([\n EdgeType.CALLS,\n EdgeType.CONNECTS_TO,\n EdgeType.PUBLISHES_TO,\n EdgeType.CONSUMES_FROM,\n])\n\nfunction detectMissingDivergences(\n graph: NeatGraph,\n bucket: EdgeBucket,\n): Divergence[] {\n const out: Divergence[] = []\n\n // CONTAINS is structural ownership (service → file), not a declared-vs-\n // observed relationship — comparing its tiers would surface an OTel-only\n // file node as a spurious missing-extracted finding (file-awareness.md §2).\n // Divergence compares CALLS-family edges at the shared grain (§7).\n if (bucket.type === EdgeType.CONTAINS) return out\n\n if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {\n // Skip when the would-be target is a FrontierNode — those represent\n // unresolved span peers, not real entities we expect OBSERVED traffic\n // to. The coexistence contract is between EXTRACTED and OBSERVED on\n // real nodes; FRONTIER is unknown territory.\n if (!nodeIsFrontier(graph, bucket.target)) {\n // ADR-066 §4 — weight by the EXTRACTED edge's graded confidence.\n // Substring/hostname-shape candidates already dropped at the precision\n // floor; what remains is structural or verified-call-site evidence.\n out.push({\n type: 'missing-observed',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n extracted: bucket.extracted,\n confidence: gradedConfidence(bucket.extracted),\n reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_OBSERVED,\n })\n }\n }\n\n if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {\n // ADR-066 §4 — cascade from the OBSERVED edge's graded confidence.\n // OBSERVED-led finding; the headline divergence type. A WebSocketChannelNode\n // target is skipped: it is OBSERVED-only by design (ADR-125), so an\n // OBSERVED-only CONNECTS_TO onto it is expected, not a missing-extracted\n // divergence — mirrors the CONTAINS exclusion above, keyed on target type.\n out.push({\n type: 'missing-extracted',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n observed: bucket.observed,\n confidence: gradedConfidence(bucket.observed),\n reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_EXTRACTED,\n })\n }\n\n return out\n}\n\n// Returns the declared host of the service's static DB target, when\n// recoverable. ServiceNode.dbConnectionTarget is the static-extraction\n// surface for \"this service connects to X\" — `X` is host[:port] or a\n// docker-compose-style service name. Empty / undefined means we have no\n// EXTRACTED host to compare against and host-mismatch can't fire.\nfunction declaredHostFor(svc: ServiceNode): string | null {\n const raw = svc.dbConnectionTarget?.trim()\n if (!raw) return null\n // Strip a trailing port if present so it lines up with DatabaseNode.host\n // (ADR-028 §6 — DatabaseNode id excludes port).\n const colon = raw.lastIndexOf(':')\n if (colon === -1) return raw\n const port = raw.slice(colon + 1)\n if (/^\\d+$/.test(port)) return raw.slice(0, colon)\n return raw\n}\n\nfunction hasExtractedConfiguredBy(graph: NeatGraph, svcId: string): boolean {\n for (const edgeId of graph.outboundEdges(svcId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONFIGURED_BY && e.provenance === Provenance.EXTRACTED) {\n return true\n }\n }\n return false\n}\n\nfunction detectHostMismatch(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const declaredHost = declaredHostFor(svc)\n if (!declaredHost) return []\n if (!hasExtractedConfiguredBy(graph, svcId)) return []\n\n const out: Divergence[] = []\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n const observedHost = target.host?.trim()\n if (!observedHost) continue\n if (observedHost === declaredHost) continue\n\n out.push({\n type: 'host-mismatch',\n source: svcId,\n target: edge.target,\n extractedHost: declaredHost,\n observedHost,\n confidence: clampConfidence(confidenceForEdge(edge)),\n reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,\n recommendation: RECOMMENDATION_HOST_MISMATCH,\n })\n }\n return out\n}\n\nfunction detectCompatDivergences(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const out: Divergence[] = []\n const deps = svc.dependencies ?? {}\n\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n\n // Driver-engine compat. Definitive — when a rule fires it's a\n // version-mismatch with confidence 1.0.\n for (const pair of compatPairs()) {\n if (pair.engine !== target.engine) continue\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(\n pair.driver,\n declared,\n target.engine,\n target.engineVersion,\n )\n if (!result.compatible && result.reason) {\n out.push({\n type: 'version-mismatch',\n source: svcId,\n target: edge.target,\n extractedVersion: declared,\n observedVersion: target.engineVersion,\n compatibility: 'incompatible',\n confidence: 1.0,\n reason: result.reason,\n recommendation: result.minDriverVersion\n ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.`\n : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`,\n })\n }\n }\n\n // Deprecated-api compat. Broader than version-mismatch — surfaces as\n // compat-violation. Driver-engine rules above already covered the\n // \"version is too low\" shape; deprecated covers \"version is too high\n // / no longer supported.\"\n for (const rule of deprecatedApis()) {\n const declared = deps[rule.package]\n if (!declared) continue\n const result = checkDeprecatedApi(rule, declared)\n if (!result.compatible && result.reason) {\n const ruleRef: CompatRuleRef = {\n kind: rule.kind ?? 'deprecated-api',\n reason: result.reason,\n package: rule.package,\n }\n out.push({\n type: 'compat-violation',\n source: svcId,\n target: edge.target,\n rule: ruleRef,\n observed: edge,\n confidence: 1.0,\n reason: result.reason,\n recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`,\n })\n }\n }\n }\n return out\n}\n\nfunction involvesNode(d: Divergence, nodeId: string): boolean {\n return d.source === nodeId || d.target === nodeId\n}\n\n// A single service<->DB host drift lights up three ways: the host-mismatch\n// itself, a missing-extracted on the observed DB node (the OBSERVED CONNECTS_TO\n// edge went to the *wrong* host, so it has no EXTRACTED twin), and a\n// missing-observed on the declared DB node (the EXTRACTED wire to the declared\n// host was never driven because traffic went elsewhere). The two missing-*\n// findings are just the halves of the drift the host-mismatch already names in\n// full, so they collapse into it — one divergence per distinct problem, so\n// blast-radius counts stay honest (issue #591). Pass 1 and Pass 2 run\n// independently, so this reconciles them after both have emitted.\nfunction suppressHostMismatchHalves(all: Divergence[]): Divergence[] {\n const observedHalf = new Set<string>() // `${source}->${target}` of the observed DB edge\n const declaredHalf = new Set<string>() // declared DB node id\n for (const d of all) {\n if (d.type !== 'host-mismatch') continue\n observedHalf.add(`${d.source}->${d.target}`)\n declaredHalf.add(databaseId(d.extractedHost))\n }\n if (observedHalf.size === 0) return all\n return all.filter((d) => {\n if (\n d.type === 'missing-extracted' &&\n observedHalf.has(`${d.source}->${d.target}`)\n ) {\n return false\n }\n if (d.type === 'missing-observed' && declaredHalf.has(d.target)) return false\n return true\n })\n}\n\nexport function computeDivergences(\n graph: NeatGraph,\n opts: DivergenceQueryOpts = {},\n): DivergenceResult {\n const all: Divergence[] = []\n\n // Pass 1 — bucket every edge and emit missing-observed / missing-extracted.\n const buckets = bucketEdges(graph)\n for (const bucket of buckets.values()) {\n for (const d of detectMissingDivergences(graph, bucket)) all.push(d)\n }\n\n // Pass 2 — per-service host + compat rules.\n graph.forEachNode((nodeId, attrs) => {\n const n = attrs as GraphNode\n if (n.type !== NodeType.ServiceNode) return\n const svc = n as ServiceNode\n for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d)\n for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d)\n })\n\n // Reconcile the two passes: a fired host-mismatch already tells the whole\n // service<->DB drift story, so drop the redundant missing-* halves (#591).\n const reconciled = suppressHostMismatchHalves(all)\n\n // Filter + sort. Higher confidence first; within the same confidence,\n // stable on (type, source, target) so callers see deterministic output.\n let filtered = reconciled\n if (opts.type) {\n const allowed = opts.type\n filtered = filtered.filter((d) => allowed.has(d.type))\n }\n if (opts.minConfidence !== undefined) {\n const threshold = opts.minConfidence\n filtered = filtered.filter((d) => d.confidence >= threshold)\n }\n if (opts.node) {\n const target = opts.node\n filtered = filtered.filter((d) => involvesNode(d, target))\n }\n\n // ADR-066 §4 / §5 — confidence desc; missing-extracted leads\n // missing-observed at equal confidence (OBSERVED-led tiebreaker); then\n // stable on (type, source, target).\n const TYPE_LEADERSHIP: Record<DivergenceType, number> = {\n 'missing-extracted': 0,\n 'missing-observed': 1,\n 'version-mismatch': 2,\n 'host-mismatch': 3,\n 'compat-violation': 4,\n }\n filtered.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence\n const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type]\n if (lead !== 0) return lead\n if (a.type !== b.type) return a.type.localeCompare(b.type)\n if (a.source !== b.source) return a.source.localeCompare(b.source)\n return a.target.localeCompare(b.target)\n })\n\n return DivergenceResultSchema.parse({\n divergences: filtered,\n totalAffected: filtered.length,\n computedAt: new Date().toISOString(),\n })\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { Provenance, observedEdgeId } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\nexport const SCHEMA_VERSION = 4\n\nexport interface PersistedGraph {\n schemaVersion: number\n exportedAt: string\n graph: ReturnType<NeatGraph['export']>\n}\n\n// v1 → v2: ServiceNode shed `pgDriverVersion` (ADR-019). Compat traversal reads\n// `dependencies[driver]` instead. Strip the field from any v1 snapshot rather\n// than hard-failing — a stale snapshot on disk shouldn't cost a re-extract.\nfunction migrateV1ToV2(payload: PersistedGraph): PersistedGraph {\n const nodes = (payload.graph as { nodes?: Array<{ attributes?: Record<string, unknown> }> })\n .nodes\n if (Array.isArray(nodes)) {\n for (const node of nodes) {\n if (node.attributes && 'pgDriverVersion' in node.attributes) {\n delete node.attributes.pgDriverVersion\n }\n }\n }\n return { ...payload, schemaVersion: 2 }\n}\n\n// v2 → v3: Provenance enum shrinks to four values (ADR-068). Any edge whose\n// provenance still carries the pre-v0.3.5 'FRONTIER' literal is rewritten to\n// Provenance.OBSERVED on load. The target ref is unchanged — FrontierNodes\n// remain placeholders for unresolved peers; only how the edge was labelled\n// changes. If the edge id still carries the legacy provenance segment, it\n// is re-keyed to the OBSERVED wire format so consumers that parse the id\n// (traversal, divergence query, MCP) see the same shape downstream.\n//\n// The 'FRONTIER' string literal here is the only place in the codebase that\n// recognises the legacy value; the Rule 1 contract scan exempts persist.ts\n// for exactly this reason.\n// v3 → v4: ServiceNode identity gains an optional env discriminator\n// (ADR-074 §2). The v4 wire format reads as a superset of v3 — the\n// env-less `service:<name>` form is preserved as the env=`'unknown'`\n// node and the v3 → v4 migration is a version-only bump.\n//\n// Edges and node ids that pre-date the env discriminator remain valid v4\n// ids; no rewrite is needed. Idempotent — re-running on a v4 snapshot\n// produces an identical payload.\nfunction migrateV3ToV4(payload: PersistedGraph): PersistedGraph {\n return { ...payload, schemaVersion: 4 }\n}\n\nfunction migrateV2ToV3(payload: PersistedGraph): PersistedGraph {\n const edges = (payload.graph as {\n edges?: Array<{\n key?: string\n attributes?: Record<string, unknown>\n }>\n }).edges\n if (Array.isArray(edges)) {\n for (const edge of edges) {\n const attrs = edge.attributes\n // 'FRONTIER' is the pre-v0.3.5 literal — read-only here, never written\n // by current producers. The rewrite swaps to Provenance.OBSERVED.\n if (!attrs || attrs.provenance !== 'FRONTIER') continue\n attrs.provenance = Provenance.OBSERVED\n const type = typeof attrs.type === 'string' ? attrs.type : undefined\n const source = typeof attrs.source === 'string' ? attrs.source : undefined\n const target = typeof attrs.target === 'string' ? attrs.target : undefined\n if (type && source && target) {\n const newId = observedEdgeId(source, target, type)\n attrs.id = newId\n if (edge.key) edge.key = newId\n }\n }\n }\n return { ...payload, schemaVersion: 3 }\n}\n\nasync function ensureDir(filePath: string): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n}\n\nexport async function saveGraphToDisk(graph: NeatGraph, outPath: string): Promise<void> {\n await ensureDir(outPath)\n const payload: PersistedGraph = {\n schemaVersion: SCHEMA_VERSION,\n exportedAt: new Date().toISOString(),\n graph: graph.export(),\n }\n // Atomic write: drop into <name>.tmp first, then rename. A crash mid-write\n // leaves the previous snapshot intact instead of a half-truncated file.\n const tmp = `${outPath}.tmp`\n await fs.writeFile(tmp, JSON.stringify(payload), 'utf8')\n await fs.rename(tmp, outPath)\n}\n\nexport async function loadGraphFromDisk(graph: NeatGraph, outPath: string): Promise<void> {\n let raw: string\n try {\n raw = await fs.readFile(outPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n throw err\n }\n let payload = JSON.parse(raw) as PersistedGraph\n if (payload.schemaVersion === 1) {\n payload = migrateV1ToV2(payload)\n }\n if (payload.schemaVersion === 2) {\n payload = migrateV2ToV3(payload)\n }\n if (payload.schemaVersion === 3) {\n payload = migrateV3ToV4(payload)\n }\n if (payload.schemaVersion !== SCHEMA_VERSION) {\n throw new Error(\n `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`,\n )\n }\n graph.clear()\n graph.import(payload.graph)\n}\n\nexport interface PersistLoopOptions {\n // How often the periodic background save fires. Defaults to 60s.\n intervalMs?: number\n // Whether a SIGTERM/SIGINT should flush the graph and then exit the process.\n // Defaults to true, which is what the standalone owners (`neat serve`,\n // `neat watch`) want: the persist loop is the only thing holding the process\n // open, so it owns the shutdown — flush, then exit.\n //\n // The daemon owns its own orderly shutdown (it closes its listeners, flushes\n // every slot, clears its `daemon.json` + the machine-wide discovery copy, and\n // removes its pid file before exiting). It runs many persist loops, one per\n // project, and an exiting signal handler inside any of them would end the\n // process out from under that teardown — leaving the discovery copy and pid\n // file behind. So the daemon passes `false`: each loop still saves on its\n // interval and unhooks cleanly on teardown, but the daemon decides when the\n // process exits.\n exitOnSignal?: boolean\n}\n\n// Periodic save + (optionally) a best-effort save-and-exit on SIGTERM/SIGINT.\n// Returns a cleanup that clears the interval and unhooks any signal handlers —\n// important for tests so they don't keep the process alive, and for the daemon\n// so a torn-down slot's loop stops reacting to signals.\nexport function startPersistLoop(\n graph: NeatGraph,\n outPath: string,\n opts: PersistLoopOptions = {},\n): () => void {\n const intervalMs = opts.intervalMs ?? 60_000\n const exitOnSignal = opts.exitOnSignal ?? true\n let stopped = false\n\n const tick = async (): Promise<void> => {\n if (stopped) return\n try {\n await saveGraphToDisk(graph, outPath)\n } catch (err) {\n console.error('persist: periodic save failed', err)\n }\n }\n\n const interval = setInterval(() => {\n void tick()\n }, intervalMs)\n\n const onSignal = exitOnSignal\n ? (signal: NodeJS.Signals): void => {\n void (async () => {\n try {\n await saveGraphToDisk(graph, outPath)\n } catch (err) {\n console.error(`persist: ${signal} save failed`, err)\n } finally {\n process.exit(0)\n }\n })()\n }\n : null\n\n if (onSignal) {\n process.on('SIGTERM', onSignal)\n process.on('SIGINT', onSignal)\n }\n\n return () => {\n stopped = true\n clearInterval(interval)\n if (onSignal) {\n process.off('SIGTERM', onSignal)\n process.off('SIGINT', onSignal)\n }\n }\n}\n\n// Snapshot merge (ADR-074 §1) lives in ingest.ts — that's the mutation-\n// authority boundary per the lifecycle contract (Rule 3 / ADR-030). The\n// merge is a form of ingestion: an external snapshot lands on the live graph\n// the same way an OTel span does, preserving the EXTRACTED + OBSERVED\n// coexistence contract along the way.\n","import { promises as fs } from 'node:fs'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\n// Diff a snapshot on disk (or fetched over HTTP) against the live in-memory\n// graph. The \"base\" is the snapshot you supplied; the \"current\" is whatever\n// the server has loaded right now. Symmetric in shape, but consumers usually\n// want to read it as \"what changed since base.\"\n//\n// The shape mirrors the issue's spec (#77):\n// { added: { nodes, edges }, removed: { nodes, edges },\n// changed: { nodes: [{id, before, after}], edges: [{id, before, after}] } }\n//\n// Both timestamps are echoed back so the caller doesn't have to track them\n// separately.\n\ninterface PersistedNodeEntry {\n key?: string\n attributes?: Record<string, unknown>\n}\n\ninterface PersistedEdgeEntry {\n key?: string\n source?: string\n target?: string\n attributes?: Record<string, unknown>\n}\n\nexport interface PersistedSnapshot {\n schemaVersion?: number\n exportedAt?: string\n graph?: {\n nodes?: PersistedNodeEntry[]\n edges?: PersistedEdgeEntry[]\n }\n}\n\nexport interface GraphDiff {\n base: { exportedAt?: string }\n current: { exportedAt: string }\n added: { nodes: GraphNode[]; edges: GraphEdge[] }\n removed: { nodes: GraphNode[]; edges: GraphEdge[] }\n changed: {\n nodes: { id: string; before: GraphNode; after: GraphNode }[]\n edges: { id: string; before: GraphEdge; after: GraphEdge }[]\n }\n}\n\nexport async function loadSnapshotForDiff(target: string): Promise<PersistedSnapshot> {\n if (/^https?:\\/\\//i.test(target)) {\n const res = await fetch(target)\n if (!res.ok) {\n throw new Error(`fetch ${target} failed: ${res.status} ${res.statusText}`)\n }\n return (await res.json()) as PersistedSnapshot\n }\n const raw = await fs.readFile(target, 'utf8')\n return JSON.parse(raw) as PersistedSnapshot\n}\n\nfunction indexEntries<T>(\n entries: { key?: string; attributes?: Record<string, unknown> }[] | undefined,\n): Map<string, T> {\n const m = new Map<string, T>()\n if (!entries) return m\n for (const entry of entries) {\n const id = (entry.attributes?.id as string | undefined) ?? entry.key\n if (!id) continue\n m.set(id, entry.attributes as T)\n }\n return m\n}\n\nexport function computeGraphDiff(\n liveGraph: NeatGraph,\n baseSnapshot: PersistedSnapshot,\n currentExportedAt: string = new Date().toISOString(),\n): GraphDiff {\n const baseNodes = indexEntries<GraphNode>(baseSnapshot.graph?.nodes)\n const baseEdges = indexEntries<GraphEdge>(baseSnapshot.graph?.edges)\n\n const liveNodes = new Map<string, GraphNode>()\n liveGraph.forEachNode((id, attrs) => liveNodes.set(id, attrs as GraphNode))\n const liveEdges = new Map<string, GraphEdge>()\n liveGraph.forEachEdge((id, attrs) => liveEdges.set(id, attrs as GraphEdge))\n\n const result: GraphDiff = {\n base: { exportedAt: baseSnapshot.exportedAt },\n current: { exportedAt: currentExportedAt },\n added: { nodes: [], edges: [] },\n removed: { nodes: [], edges: [] },\n changed: { nodes: [], edges: [] },\n }\n\n for (const [id, after] of liveNodes) {\n const before = baseNodes.get(id)\n if (!before) {\n result.added.nodes.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.nodes.push({ id, before, after })\n }\n }\n for (const [id, before] of baseNodes) {\n if (!liveNodes.has(id)) result.removed.nodes.push(before)\n }\n for (const [id, after] of liveEdges) {\n const before = baseEdges.get(id)\n if (!before) {\n result.added.edges.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.edges.push({ id, before, after })\n }\n }\n for (const [id, before] of baseEdges) {\n if (!liveEdges.has(id)) result.removed.edges.push(before)\n }\n\n return result\n}\n\n// Stable JSON comparison. Snapshot order isn't guaranteed, so canonicalising\n// keys before stringify keeps the comparison robust against re-ordered fields.\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n return canonicalJson(a) === canonicalJson(b)\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_key, v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v as Record<string, unknown>)\n .sort()\n .reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = (v as Record<string, unknown>)[k]\n return acc\n }, {})\n }\n return v\n })\n}\n","// Project registry — owns the per-project state that lives alongside the\n// graph map: snapshot/error/stale paths, scan path, optional search index.\n//\n// Routes use it via `resolve(project)`. Server / watch construct it once at\n// boot and pass it to buildApi. Tests can mock it with a small literal.\n\nimport path from 'node:path'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT, getGraph } from './graph.js'\nimport type { SearchIndex } from './search.js'\n\nexport interface ProjectPaths {\n snapshotPath: string\n errorsPath: string\n staleEventsPath: string\n embeddingsCachePath: string\n // Policy-violations log per ADR-041 § Append-only ndjson sidecars. Lives\n // in the same neat-out directory as the other ndjson sidecars; daemons\n // wire PolicyViolationsLog to it.\n policyViolationsPath: string\n}\n\n// Default project keeps the legacy filenames so existing M5 / β / γ users\n// see no behaviour change. Named projects fan out by name (ADR-026).\nexport function pathsForProject(project: string, baseDir: string): ProjectPaths {\n if (project === DEFAULT_PROJECT) {\n return {\n snapshotPath: path.join(baseDir, 'graph.json'),\n errorsPath: path.join(baseDir, 'errors.ndjson'),\n staleEventsPath: path.join(baseDir, 'stale-events.ndjson'),\n embeddingsCachePath: path.join(baseDir, 'embeddings.json'),\n policyViolationsPath: path.join(baseDir, 'policy-violations.ndjson'),\n }\n }\n return {\n snapshotPath: path.join(baseDir, `${project}.json`),\n errorsPath: path.join(baseDir, `errors.${project}.ndjson`),\n staleEventsPath: path.join(baseDir, `stale-events.${project}.ndjson`),\n embeddingsCachePath: path.join(baseDir, `embeddings.${project}.json`),\n policyViolationsPath: path.join(baseDir, `policy-violations.${project}.ndjson`),\n }\n}\n\nexport interface ProjectContext {\n name: string\n graph: NeatGraph\n scanPath?: string\n paths: ProjectPaths\n searchIndex?: SearchIndex\n}\n\nexport class Projects {\n private contexts = new Map<string, ProjectContext>()\n\n upsert(ctx: ProjectContext): void {\n this.contexts.set(ctx.name, ctx)\n }\n\n set(\n name: string,\n init: Omit<ProjectContext, 'name' | 'graph'> & { graph?: NeatGraph },\n ): ProjectContext {\n const ctx: ProjectContext = {\n name,\n graph: init.graph ?? getGraph(name),\n scanPath: init.scanPath,\n paths: init.paths,\n searchIndex: init.searchIndex,\n }\n this.contexts.set(name, ctx)\n return ctx\n }\n\n get(name: string): ProjectContext | undefined {\n return this.contexts.get(name)\n }\n\n has(name: string): boolean {\n return this.contexts.has(name)\n }\n\n list(): string[] {\n return [...this.contexts.keys()].sort()\n }\n\n attachSearchIndex(name: string, index: SearchIndex | undefined): void {\n const ctx = this.contexts.get(name)\n if (ctx) ctx.searchIndex = index\n }\n}\n\n// Parses NEAT_PROJECTS=a,b,c; trims whitespace, drops empty entries.\n// `default` is implicit (always loaded), so callers usually filter it out\n// before iterating extra projects.\nexport function parseExtraProjects(raw: string | undefined): string[] {\n if (!raw) return []\n return raw\n .split(',')\n .map((p) => p.trim())\n .filter((p) => p.length > 0 && p !== DEFAULT_PROJECT)\n}\n","/**\n * Machine-level project registry + machine-wide daemon discovery.\n *\n * This module owns two surfaces under `~/.neat/`:\n *\n * 1. The legacy project registry at `~/.neat/projects.json` (ADR-048). One\n * file, per-user, machine-local, not synced. Under the project-daemon\n * contract (ADR-096) it is no longer the coordination point — it is read\n * once for migration and otherwise left to the additive writes the daemon\n * and orchestrator still perform. The read-modify-write helpers below keep\n * their atomic-write + exclusive-lock machinery for that legacy surface.\n *\n * 2. The machine-wide daemon discovery directory at `~/.neat/daemons/`\n * (ADR-096 §6). One file per running daemon — `<project>.json` — each owned\n * solely by the daemon that wrote it (on start) and removes it (on graceful\n * stop). Discovery is **append-only and lock-free**: a reader scans the\n * directory and reconciles liveness; it never acquires a shared lock, so it\n * can never deadlock against a daemon (#506). Losing or rebuilding the\n * directory costs discovery convenience, not correctness — each project's\n * own `neat-out/daemon.json` stays authoritative.\n *\n * `neat ps` / `neat list` and the per-daemon `pause` / `resume` / `uninstall`\n * verbs read discovery, falling back to the legacy registry where no daemon\n * file is present yet (the migration window before every daemon self-describes).\n *\n * The legacy lock is a file we exclusively-create (`O_EXCL`), hold while we\n * mutate, and unlink on the way out. Crude but cross-platform; matches what\n * `proper-lockfile` does internally without pulling the dep in. It never sits\n * on the discovery path.\n */\n\nimport { promises as fs } from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport {\n RegistryFileSchema,\n type RegistryEntry,\n type RegistryFile,\n type RegistryStatus,\n} from '@neat.is/types'\n\nconst LOCK_TIMEOUT_MS = 5_000\nconst LOCK_RETRY_MS = 50\n\n// Resolve `~/.neat/` per call so tests can override `HOME` / `NEAT_HOME`\n// before each run without module-load order mattering.\nfunction neatHome(): string {\n const override = process.env.NEAT_HOME\n if (override && override.length > 0) return path.resolve(override)\n return path.join(os.homedir(), '.neat')\n}\n\nexport function registryPath(): string {\n return path.join(neatHome(), 'projects.json')\n}\n\nexport function registryLockPath(): string {\n return path.join(neatHome(), 'projects.json.lock')\n}\n\n// The daemon writes its PID here on startup (daemon.ts). It's the authoritative\n// \"is a neat daemon running\" signal — more reliable than matching the lock's\n// own PID, since the daemon holds the registry lock only for brief read-modify-\n// write windows and an init that contends usually catches an orphaned lock\n// rather than the daemon mid-write.\nfunction daemonPidPath(): string {\n return path.join(neatHome(), 'neatd.pid')\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #366 / ADR-096 §6 — machine-wide daemon discovery, lock-free.\n//\n// Each running daemon drops a `<project>.json` file into `~/.neat/daemons/`\n// on start and removes it on graceful stop. The file is a copy of that\n// project's authoritative `neat-out/daemon.json` record. A daemon owns only\n// its own file — there is no shared file and no shared lock. Discovery reads\n// the directory and reconciles liveness; it never acquires a lock, so it can\n// never deadlock against a daemon mid-write the way the registry lock did\n// (#506). The shape mirrors what the daemon writes (keystone #508); we read it\n// as plain JSON rather than importing the daemon's writer so the two stay\n// decoupled, and validate defensively so a malformed file is skipped, not\n// fatal.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface DaemonPorts {\n rest: number\n otlp: number\n web: number\n}\n\nexport interface DaemonRecord {\n project: string\n // Project root whose `neat-out/` holds the authoritative record.\n projectPath: string\n pid: number\n status: 'running' | 'stopped'\n ports: DaemonPorts\n // ISO8601.\n startedAt: string\n neatVersion: string\n}\n\n// A discovered daemon plus the liveness verdict the reader reconciled. `live`\n// is true only when the record claims `running` AND its pid is actually alive\n// — a daemon that crashed without clearing its file reads as `running` but\n// `live: false`, so `neat ps` never reports a ghost as up.\nexport interface DiscoveredDaemon {\n record: DaemonRecord\n live: boolean\n // Where the discovery copy was read from. Useful for diagnostics and for the\n // per-daemon verbs that clean a stale file.\n source: string\n}\n\nexport function daemonsDir(): string {\n return path.join(neatHome(), 'daemons')\n}\n\nfunction isFiniteInt(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v)\n}\n\n// Parse one discovery file's contents into a DaemonRecord, or undefined when\n// the shape doesn't hold. Defensive on purpose: discovery is convenience, so a\n// half-written or hand-edited file is skipped rather than crashing `neat ps`.\nfunction parseDaemonRecord(raw: string): DaemonRecord | undefined {\n let obj: unknown\n try {\n obj = JSON.parse(raw)\n } catch {\n return undefined\n }\n if (typeof obj !== 'object' || obj === null) return undefined\n const r = obj as Record<string, unknown>\n const ports = r.ports as Record<string, unknown> | undefined\n if (\n typeof r.project !== 'string' ||\n typeof r.projectPath !== 'string' ||\n !isFiniteInt(r.pid) ||\n (r.status !== 'running' && r.status !== 'stopped') ||\n typeof r.startedAt !== 'string' ||\n typeof r.neatVersion !== 'string' ||\n !ports ||\n !isFiniteInt(ports.rest) ||\n !isFiniteInt(ports.otlp) ||\n !isFiniteInt(ports.web)\n ) {\n return undefined\n }\n return {\n project: r.project,\n projectPath: r.projectPath,\n pid: r.pid,\n status: r.status,\n ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },\n startedAt: r.startedAt,\n neatVersion: r.neatVersion,\n }\n}\n\n// Liveness for a discovered daemon. Exported (via the default probe) so the\n// verbs and tests reconcile the same way: `running` claim AND pid alive.\nexport function isPidAlive(pid: number): boolean {\n return isPidAliveDefault(pid)\n}\n\nexport interface DiscoveryProbe {\n isPidAlive(pid: number): boolean\n}\n\nconst defaultDiscoveryProbe: DiscoveryProbe = { isPidAlive: isPidAliveDefault }\n\n/**\n * Scan `~/.neat/daemons/` and return every well-formed discovery record with\n * its reconciled liveness. Lock-free: a plain directory read, no rendezvous.\n *\n * A missing directory (no daemon has ever started under this model) yields an\n * empty list — first run, nothing to discover. Malformed or unreadable files\n * are skipped silently; discovery degrades to \"fewer entries,\" never an error.\n */\nexport async function discoverDaemons(\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<DiscoveredDaemon[]> {\n const dir = daemonsDir()\n let names: string[]\n try {\n names = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n const out: DiscoveredDaemon[] = []\n for (const name of names) {\n if (!name.endsWith('.json')) continue\n const file = path.join(dir, name)\n let raw: string\n try {\n raw = await fs.readFile(file, 'utf8')\n } catch {\n continue\n }\n const record = parseDaemonRecord(raw)\n if (!record) continue\n const live = record.status === 'running' && probe.isPidAlive(record.pid)\n out.push({ record, live, source: file })\n }\n out.sort((a, b) => a.record.project.localeCompare(b.record.project))\n return out\n}\n\n/**\n * Remove a daemon's discovery file. A daemon owns its own file, so this is for\n * the per-daemon verbs reconciling a record whose daemon is gone (a crashed\n * daemon that never cleared its own file) — never a rendezvous another process\n * coordinates through. Best-effort: an already-absent file is success.\n */\nexport async function removeDaemonRecord(source: string): Promise<void> {\n await fs.unlink(source).catch(() => {})\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #366 / ADR-096 §8 — migration off the global registry.\n//\n// Installs carrying `~/.neat/projects.json` map their registered projects onto\n// per-project daemons on first run under this model. The discovery directory is\n// authoritative for \"what's running\"; the registry is read once to surface\n// projects that were registered before any daemon self-described, so the\n// machine-wide verbs still see them during the migration window. The registry\n// is no longer the coordination surface — nothing here writes it back.\n// ─────────────────────────────────────────────────────────────────────────\n\n// A unified view for the machine-wide verbs: a discovered daemon when one is\n// present, otherwise a legacy registry entry projected into the same shape so\n// `neat ps` / `neat list` render one table regardless of which surface the\n// project lives on yet.\nexport interface MachineProject {\n project: string\n projectPath: string\n // 'running' / 'stopped' come from a live discovery record; 'registered'\n // marks a legacy registry entry with no daemon file yet (migration window).\n state: 'running' | 'stopped' | 'registered'\n // Present only when a discovery record backs this row.\n ports?: DaemonPorts\n // Legacy registry status when the row came from the registry; undefined for\n // a discovery-backed row.\n registryStatus?: RegistryStatus\n pid?: number\n}\n\n/**\n * The machine-wide project view the CLI verbs render. Discovery wins: every\n * `~/.neat/daemons/` record becomes a row (running or stopped per liveness).\n * Legacy registry entries whose path isn't already covered by a discovery\n * record are folded in as `registered` rows so a pre-#508 install still lists\n * its projects. Keyed on resolved path so a registry entry and its daemon\n * record collapse to one row.\n *\n * Read-only on both surfaces. The registry is read once for migration; nothing\n * here writes it back, in keeping with ADR-096 §8.\n */\nexport async function listMachineProjects(\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<MachineProject[]> {\n const discovered = await discoverDaemons(probe)\n const byPath = new Map<string, MachineProject>()\n for (const d of discovered) {\n const key = await normalizeProjectPath(d.record.projectPath)\n byPath.set(key, {\n project: d.record.project,\n projectPath: d.record.projectPath,\n state: d.live ? 'running' : 'stopped',\n ports: d.record.ports,\n pid: d.record.pid,\n })\n }\n\n // Migration read: legacy registry entries not already covered by a daemon\n // record. Read once, never written back.\n let legacy: RegistryEntry[] = []\n try {\n legacy = (await readRegistry()).projects\n } catch {\n // A corrupt legacy file shouldn't sink discovery — the daemons we found are\n // still valid. Skip the legacy fold.\n legacy = []\n }\n for (const entry of legacy) {\n const key = await normalizeProjectPath(entry.path)\n if (byPath.has(key)) continue\n byPath.set(key, {\n project: entry.name,\n projectPath: entry.path,\n state: 'registered',\n registryStatus: entry.status,\n })\n }\n\n return [...byPath.values()].sort((a, b) => a.project.localeCompare(b.project))\n}\n\n// Find a discovery record by project name, with its reconciled liveness. The\n// per-daemon verbs key on the project name the operator types; discovery is the\n// source of truth for whether a daemon is running and which pid to signal.\nexport async function findDaemonByProject(\n name: string,\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<DiscoveredDaemon | undefined> {\n const discovered = await discoverDaemons(probe)\n return discovered.find((d) => d.record.project === name)\n}\n\n// Signal a running daemon to shut down via its discovery-recorded pid. SIGTERM\n// is the graceful-stop signal every daemon already wires (neatd.ts) — the\n// daemon clears its own discovery file on the way out. Returns true when the\n// signal was delivered, false when the pid was already gone (nothing to stop).\nexport function signalDaemonStop(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM')\n return true\n } catch (err) {\n // ESRCH → already gone; treat as a no-op success of intent. EPERM and the\n // rest surface as \"couldn't signal\" so the verb can report honestly.\n if ((err as NodeJS.ErrnoException).code === 'ESRCH') return false\n throw err\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #432 — distinguish a live daemon from a genuinely stale lock.\n//\n// `neat init` used to hang the full timeout on any contended lock and then\n// print one remediation: \"remove the file by hand.\" That advice is actively\n// dangerous when a neat daemon is alive — the daemon grabs the lock for its\n// own registry writes, so hand-removing it races the daemon and corrupts the\n// registry for the live process. The lock now carries the holder PID, and the\n// timeout (or first contention with a live daemon) resolves to a message that\n// names who holds it and what's safe to do.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport type LockHolder =\n | { kind: 'daemon'; pid: number }\n | { kind: 'command'; pid: number }\n | { kind: 'stale' }\n\n// Probes the holder-resolution logic depends on, broken out so tests can drive\n// each branch without a real daemon or live PIDs.\nexport interface LockHolderProbe {\n // POSIX liveness via `process.kill(pid, 0)`: true if the process exists\n // (including EPERM — alive but owned by another user), false if it's gone.\n isPidAlive(pid: number): boolean\n // PID recorded in `~/.neat/neatd.pid`, or undefined if there's no pidfile.\n daemonPidFromFile(): Promise<number | undefined>\n // Whether the daemon's always-open `/health` endpoint answers. Confirms a\n // live neatd.pid is actually our daemon and not a reused PID.\n daemonResponds(): Promise<boolean>\n}\n\nfunction isPidAliveDefault(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (err) {\n // ESRCH → no such process (dead). EPERM → exists but owned by another user\n // (alive). Treat anything else as not-alive: we'd rather under-claim a live\n // holder than wrongly block on a lock we can't verify.\n return (err as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nasync function readPidFile(file: string): Promise<number | undefined> {\n try {\n const raw = await fs.readFile(file, 'utf8')\n const pid = Number.parseInt(raw.trim(), 10)\n return Number.isInteger(pid) && pid > 0 ? pid : undefined\n } catch {\n return undefined\n }\n}\n\nconst defaultLockHolderProbe: LockHolderProbe = {\n isPidAlive: isPidAliveDefault,\n daemonPidFromFile: () => readPidFile(daemonPidPath()),\n async daemonResponds(): Promise<boolean> {\n const base = process.env.NEAT_API_URL ?? 'http://localhost:8080'\n try {\n // `/health` stays unauthenticated in every mode (auth.ts), so a reachable\n // daemon answers it even with a bearer token set. Any HTTP response — not\n // just 2xx — means a daemon owns the port. A short timeout keeps the\n // fail-fast path well under a second.\n await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) })\n return true\n } catch {\n return false\n }\n },\n}\n\n// Read the PID a lock holder recorded when it created the lock. Undefined for a\n// legacy empty lock, an unreadable file, or a lock unlinked out from under us.\nasync function readLockPid(lockPath: string): Promise<number | undefined> {\n return readPidFile(lockPath)\n}\n\n// Decide who holds (or orphaned) the lock. A live daemon dominates: while neatd\n// is alive, hand-removing the lock is never safe, so we surface the daemon\n// message even when the lock itself is an empty orphan. We never classify our\n// own process as the blocking daemon — a daemon serializing two of its own\n// registry writes contends with itself briefly and should just retry.\nexport async function classifyLockHolder(\n lockPath: string,\n probe: LockHolderProbe = defaultLockHolderProbe,\n): Promise<LockHolder> {\n const lockPid = await readLockPid(lockPath)\n const daemonPid = await probe.daemonPidFromFile()\n if (\n daemonPid !== undefined &&\n daemonPid !== process.pid &&\n probe.isPidAlive(daemonPid) &&\n // The lock already names the daemon, or the daemon answers on its port.\n // Either confirms a live daemon is in the picture (the second guards\n // against a stale pidfile whose PID got reused).\n (daemonPid === lockPid || (await probe.daemonResponds()))\n ) {\n return { kind: 'daemon', pid: daemonPid }\n }\n if (lockPid !== undefined && lockPid !== process.pid && probe.isPidAlive(lockPid)) {\n return { kind: 'command', pid: lockPid }\n }\n return { kind: 'stale' }\n}\n\nexport function lockHolderMessage(holder: LockHolder, lockPath: string, timeoutMs: number): string {\n switch (holder.kind) {\n case 'daemon':\n return (\n `The neat daemon (pid ${holder.pid}) is holding the registry lock. ` +\n 'Register this project through the daemon, or stop neatd and re-run `neat init`.'\n )\n case 'command':\n return (\n `Another neat command (pid ${holder.pid}) is holding the registry lock. ` +\n \"Wait for it to finish, or check `ps` if you're not sure what's running.\"\n )\n case 'stale':\n return (\n `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. ` +\n 'Another neat process is holding the lock; if no such process exists, remove the file by hand.'\n )\n }\n}\n\n/**\n * Path normalisation per ADR-048 #7. Two `init` calls from different relative\n * paths to the same dir must collapse to one entry. `path.resolve` handles\n * relative-to-cwd; we pass it through `fs.realpath` when the dir exists so\n * symlinked paths land on the same canonical entry too.\n */\nexport async function normalizeProjectPath(input: string): Promise<string> {\n const resolved = path.resolve(input)\n try {\n return await fs.realpath(resolved)\n } catch {\n return resolved\n }\n}\n\n/**\n * tmp + fsync + rename. The fsync on the data fd guarantees the bytes are on\n * disk before rename swaps the inode; rename itself is atomic on POSIX.\n *\n * Exported so the init flow and test harnesses can use the same helper.\n */\nexport async function writeAtomically(target: string, contents: string): Promise<void> {\n await fs.mkdir(path.dirname(target), { recursive: true })\n const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`\n const fd = await fs.open(tmp, 'w')\n try {\n await fd.writeFile(contents, 'utf8')\n await fd.sync()\n } finally {\n await fd.close()\n }\n await fs.rename(tmp, target)\n}\n\nasync function acquireLock(\n lockPath: string,\n timeoutMs: number = LOCK_TIMEOUT_MS,\n probe: LockHolderProbe = defaultLockHolderProbe,\n): Promise<void> {\n const deadline = Date.now() + timeoutMs\n await fs.mkdir(path.dirname(lockPath), { recursive: true })\n let probedHolder = false\n while (true) {\n try {\n const fd = await fs.open(lockPath, 'wx')\n try {\n // Stamp the lock with our PID so a contender can name who holds it and\n // tell a live holder apart from a stale file. Best-effort: an empty\n // lock still excludes correctly, it just can't be diagnosed.\n await fd.writeFile(`${process.pid}\\n`, 'utf8')\n } finally {\n await fd.close()\n }\n return\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code !== 'EEXIST') throw err\n // A live daemon holds the registry continuously enough that spinning the\n // full timeout is pointless — surface the routed remediation on the first\n // contention. Peer commands and stale locks fall through to the retry:\n // peers clear on their own, and a stale lock wants the timeout's guidance.\n if (!probedHolder) {\n probedHolder = true\n const holder = await classifyLockHolder(lockPath, probe)\n if (holder.kind === 'daemon') throw new Error(lockHolderMessage(holder, lockPath, timeoutMs))\n }\n if (Date.now() >= deadline) {\n const holder = await classifyLockHolder(lockPath, probe)\n throw new Error(lockHolderMessage(holder, lockPath, timeoutMs))\n }\n await new Promise((r) => setTimeout(r, LOCK_RETRY_MS))\n }\n }\n}\n\nasync function releaseLock(lockPath: string): Promise<void> {\n await fs.unlink(lockPath).catch(() => {})\n}\n\nasync function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const lock = registryLockPath()\n await acquireLock(lock)\n try {\n return await fn()\n } finally {\n await releaseLock(lock)\n }\n}\n\n/**\n * Read the registry from disk. Returns an empty registry if the file does\n * not exist yet — first run, never registered anything.\n *\n * Throws on parse / schema errors. The contract is single-source-of-truth;\n * a corrupt file is louder than a silent reset.\n */\nexport async function readRegistry(): Promise<RegistryFile> {\n const file = registryPath()\n let raw: string\n try {\n raw = await fs.readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { version: 1, projects: [] }\n }\n throw err\n }\n const parsed = JSON.parse(raw)\n return RegistryFileSchema.parse(parsed)\n}\n\nasync function writeRegistry(reg: RegistryFile): Promise<void> {\n // Re-parse before writing to surface schema drift introduced by callers\n // mutating the in-memory object directly.\n const validated = RegistryFileSchema.parse(reg)\n await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + '\\n')\n}\n\nexport interface AddProjectOptions {\n name: string\n path: string\n languages?: string[]\n status?: RegistryStatus\n}\n\nexport class ProjectNameCollisionError extends Error {\n readonly projectName: string\n constructor(name: string) {\n super(`neat registry: a project named \"${name}\" is already registered`)\n this.name = 'ProjectNameCollisionError'\n this.projectName = name\n }\n}\n\n/**\n * Register a project, or update its `lastSeenAt` if the same path is already\n * registered under the same name (idempotent re-init).\n *\n * Hard error on name collision against a different path — ADR-046 #7. The\n * caller can recover by passing `--project <new-name>`.\n */\nexport async function addProject(opts: AddProjectOptions): Promise<RegistryEntry> {\n const resolvedPath = await normalizeProjectPath(opts.path)\n return withLock(async () => {\n const reg = await readRegistry()\n const byName = reg.projects.find((p) => p.name === opts.name)\n const byPath = reg.projects.find((p) => p.path === resolvedPath)\n\n if (byName && byName.path !== resolvedPath) {\n throw new ProjectNameCollisionError(opts.name)\n }\n\n const now = new Date().toISOString()\n\n if (byName && byName.path === resolvedPath) {\n // Idempotent re-register: same name, same path. Refresh languages /\n // status if the caller passed new ones.\n byName.lastSeenAt = now\n if (opts.languages) byName.languages = opts.languages\n if (opts.status) byName.status = opts.status\n await writeRegistry(reg)\n return byName\n }\n\n if (byPath && byPath.name !== opts.name) {\n // Same dir already registered under a different name. Treat as a\n // collision so the user is forced to decide which name wins.\n throw new ProjectNameCollisionError(byPath.name)\n }\n\n const entry: RegistryEntry = {\n name: opts.name,\n path: resolvedPath,\n registeredAt: now,\n languages: opts.languages ?? [],\n status: opts.status ?? 'active',\n }\n reg.projects.push(entry)\n await writeRegistry(reg)\n return entry\n })\n}\n\nexport async function getProject(name: string): Promise<RegistryEntry | undefined> {\n const reg = await readRegistry()\n return reg.projects.find((p) => p.name === name)\n}\n\nexport async function listProjects(): Promise<RegistryEntry[]> {\n const reg = await readRegistry()\n return reg.projects\n}\n\nexport async function setStatus(name: string, status: RegistryStatus): Promise<RegistryEntry> {\n return withLock(async () => {\n const reg = await readRegistry()\n const entry = reg.projects.find((p) => p.name === name)\n if (!entry) throw new Error(`neat registry: no project named \"${name}\"`)\n entry.status = status\n await writeRegistry(reg)\n return entry\n })\n}\n\nexport async function touchLastSeen(name: string, at: string = new Date().toISOString()): Promise<void> {\n await withLock(async () => {\n const reg = await readRegistry()\n const entry = reg.projects.find((p) => p.name === name)\n if (!entry) return\n entry.lastSeenAt = at\n await writeRegistry(reg)\n })\n}\n\n/**\n * Remove the registry entry for `name`. Per ADR-048 #6: this only removes the\n * registry row. It does **not** touch `neat-out/`, `policy.json`, or any user\n * file in the project directory. SDK-install rollback is a separate flow\n * (`neat-rollback.patch`) that the caller opts in to.\n */\nexport async function removeProject(name: string): Promise<RegistryEntry | undefined> {\n return withLock(async () => {\n const reg = await readRegistry()\n const idx = reg.projects.findIndex((p) => p.name === name)\n if (idx < 0) return undefined\n const [removed] = reg.projects.splice(idx, 1)\n await writeRegistry(reg)\n return removed\n })\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #463 — prune dead-path entries so the registry doesn't accumulate zombies.\n//\n// Ephemeral tmp-dir projects (smoke runs, throwaway demos) leave entries whose\n// `path` is gone from disk. Without pruning the daemon logs an ENOENT warning\n// for each on every startup forever and `/api/health` lists them as permanent\n// `broken` rows. Pruning removes user-facing state, so it's deliberately\n// conservative: we only ever drop an entry on a *definite* ENOENT (the path is\n// genuinely gone). A transient stat failure — EACCES, EBUSY, an unmounted\n// drive — leaves the entry untouched; it isn't dead, just unreachable.\n//\n// Two modes:\n// - Auto-prune (daemon bootstrap / reload) gates removal on a staleness TTL.\n// An ENOENT entry is only dropped if its `lastSeenAt` is older than the TTL,\n// so a recently-active project whose path is temporarily unavailable stays.\n// - `neat prune` is explicit user intent, so it drops any ENOENT entry\n// immediately regardless of age.\n// ─────────────────────────────────────────────────────────────────────────\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n// Default 7 days, mirroring NEAT_STALE_THRESHOLDS' env-override shape.\nexport const DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS\n\nexport function pruneTtlMs(): number {\n const raw = process.env.NEAT_REGISTRY_PRUNE_TTL_MS\n if (!raw) return DEFAULT_PRUNE_TTL_MS\n const n = Number.parseInt(raw, 10)\n if (Number.isFinite(n) && n >= 0) return n\n console.warn(\n `[neat] NEAT_REGISTRY_PRUNE_TTL_MS could not be parsed (${raw}); using default ${DEFAULT_PRUNE_TTL_MS}ms`,\n )\n return DEFAULT_PRUNE_TTL_MS\n}\n\n// Classify a single path: 'gone' (definite ENOENT), 'present' (stat succeeded,\n// directory exists), or 'unknown' (a transient/ambiguous error — never prune).\n// Exported so the daemon and tests can probe the same logic.\nexport type PathStatus = 'gone' | 'present' | 'unknown'\n\nasync function statPathStatus(p: string): Promise<PathStatus> {\n try {\n const stat = await fs.stat(p)\n return stat.isDirectory() ? 'present' : 'unknown'\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? 'gone' : 'unknown'\n }\n}\n\nexport interface PruneOptions {\n // Override the staleness gate. Auto-prune passes a TTL; `neat prune` passes\n // `ttlMs: 0` to drop any ENOENT entry immediately. Default is the env-driven\n // TTL.\n ttlMs?: number\n // Override the path probe (tests drive ENOENT / EACCES / present branches\n // without a real filesystem). Defaults to a real stat.\n statPath?: (p: string) => Promise<PathStatus>\n // Clock injection for the staleness comparison; defaults to Date.now().\n now?: () => number\n}\n\n/**\n * Remove registry entries whose `path` is definitely gone (ENOENT). Goes\n * through the same lock + atomic-write path as every other mutation — never a\n * raw rewrite — so the contract's atomicity invariant holds.\n *\n * Staleness gate: an ENOENT entry is dropped only when `ttlMs` is 0 (explicit\n * `neat prune`) or its `lastSeenAt` (falling back to `registeredAt`) is older\n * than `ttlMs`. A fresh ENOENT entry under a non-zero TTL is left in place — the\n * daemon still marks it `broken`, the TTL is the safety margin before removal.\n *\n * Returns the entries that were removed.\n */\nexport async function pruneRegistry(opts: PruneOptions = {}): Promise<RegistryEntry[]> {\n const ttlMs = opts.ttlMs ?? pruneTtlMs()\n const statPath = opts.statPath ?? statPathStatus\n const now = opts.now ?? Date.now\n\n return withLock(async () => {\n const reg = await readRegistry()\n const removed: RegistryEntry[] = []\n const kept: RegistryEntry[] = []\n for (const entry of reg.projects) {\n const status = await statPath(entry.path)\n if (status !== 'gone') {\n // Present, or a transient/ambiguous error — keep it. We only prune on a\n // definite ENOENT.\n kept.push(entry)\n continue\n }\n // Path is gone. Drop it immediately when there's no TTL gate, otherwise\n // only once it's been quiet longer than the TTL.\n if (ttlMs <= 0) {\n removed.push(entry)\n continue\n }\n const lastSeen = Date.parse(entry.lastSeenAt ?? entry.registeredAt)\n const age = now() - (Number.isFinite(lastSeen) ? lastSeen : 0)\n if (age > ttlMs) {\n removed.push(entry)\n } else {\n kept.push(entry)\n }\n }\n if (removed.length === 0) return []\n reg.projects = kept\n await writeRegistry(reg)\n return removed\n })\n}\n\n","import Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify'\nimport cors from '@fastify/cors'\nimport type {\n ErrorEvent,\n GraphEdge,\n GraphNode,\n Policy,\n PolicyViolation,\n RegistryEntry,\n} from '@neat.is/types'\nimport { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from '@neat.is/types'\nimport type { DivergenceType } from '@neat.is/types'\nimport {\n applyExtension,\n describeProjectInstrumentation,\n dryRunExtension,\n listUninstrumented,\n lookupInstrumentation,\n rollbackExtension,\n} from './extend/index.js'\nimport { computeDivergences } from './divergences.js'\nimport {\n evaluateAllPolicies,\n loadPolicyFile,\n PolicyViolationsLog,\n selectApplicablePolicies,\n} from './policy.js'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport { extractFromDirectory } from './extract.js'\nimport { readErrorEvents, readStaleEvents } from './ingest.js'\nimport {\n getBlastRadius,\n getObservedDependencies,\n getRootCause,\n getTransitiveDependencies,\n TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,\n TRANSITIVE_DEPENDENCIES_MAX_DEPTH,\n} from './traverse.js'\nimport { computeGraphDiff, loadSnapshotForDiff } from './diff.js'\nimport { mergeSnapshot } from './ingest.js'\nimport { SCHEMA_VERSION, type PersistedGraph } from './persist.js'\nimport type { SearchIndex } from './search.js'\nimport type { Projects, ProjectContext } from './projects.js'\nimport { Projects as ProjectsClass, pathsForProject } from './projects.js'\nimport { getProject as getRegistryProject, listProjects as listRegistryProjects } from './registry.js'\nimport { handleSse } from './streaming.js'\nimport { mountBearerAuth, readAuthEnv } from './auth.js'\n\nexport interface BuildApiOptions {\n // Multi-project shape. Optional — when absent we synthesise a single-\n // project registry from the legacy fields below so existing callers\n // (mainly tests) keep working unchanged.\n projects?: Projects\n startedAt?: number\n\n // Legacy single-project shape. Mapped to project=`default` if `projects`\n // isn't provided.\n graph?: NeatGraph\n scanPath?: string\n errorsPath?: string\n staleEventsPath?: string\n searchIndex?: SearchIndex\n\n // ADR-073 §3 — bearer token required on `/api/*` and `/events`. Undefined\n // leaves the middleware off (loopback-only callers; the bind-authority\n // gate in startDaemon refuses to bind publicly without one).\n authToken?: string\n // ADR-073 §3 — when the operator runs behind a reverse proxy that already\n // authenticates the request, the daemon-side check is bypassed.\n trustProxy?: boolean\n // ADR-073 §3 amendment — public-read mode. When `true`, GET / HEAD / OPTIONS\n // bypass the bearer check; writes still require it. OTLP ingest is gated\n // independently and is unaffected by this flag.\n publicRead?: boolean\n // Issue #340 — per-project bootstrap status. When provided, project-scoped\n // routes for projects still extracting return 503 with `{ready: false}`\n // instead of 404.\n bootstrap?: {\n status: (name: string) => 'bootstrapping' | 'active' | 'broken' | undefined\n list: () => Array<{ name: string; status: 'bootstrapping' | 'active' | 'broken'; elapsedMs: number }>\n }\n // ADR-096 §4/§5 — the per-project daemon's identity. When set, this daemon\n // serves exactly this one project: `GET /projects` reports only it (the\n // dashboard pins to \"the project this daemon serves,\" not a machine-wide\n // list), and the daemon-wide `/health` carries it at the top level for the\n // spawn-reuse identity check. Absent → the legacy multi-project daemon, whose\n // `/projects` is the machine-wide registry passthrough.\n singleProject?: { name: string; path: string }\n}\n\ninterface SerializedGraph {\n nodes: GraphNode[]\n edges: GraphEdge[]\n}\n\nfunction serializeGraph(graph: NeatGraph): SerializedGraph {\n const nodes: GraphNode[] = []\n graph.forEachNode((_id, attrs) => {\n nodes.push(attrs)\n })\n const edges: GraphEdge[] = []\n graph.forEachEdge((_id, attrs) => {\n edges.push(attrs)\n })\n return { nodes, edges }\n}\n\nfunction projectFromReq(req: FastifyRequest, singleProject?: string): string {\n // `:project` is optional in the URL — the request hits either\n // /projects/:project/X or the unprefixed /X.\n const params = req.params as { project?: string }\n const named = params.project\n // ADR-096 §4 — \"the daemon is the project.\" On a per-project daemon a bare\n // /X needs no project name to disambiguate: it means this daemon's one\n // project. The legacy `default` alias maps to it too, so an agent wired with\n // only NEAT_CORE_URL (no project arg, no NEAT_DEFAULT_PROJECT) reaches the\n // real project without naming it. An explicit real name still routes as\n // given. A non-single-project daemon keeps coercing a missing param to the\n // `default` project.\n if (singleProject) {\n return named === undefined || named === DEFAULT_PROJECT ? singleProject : named\n }\n return named ?? DEFAULT_PROJECT\n}\n\nfunction resolveProject(\n registry: Projects,\n req: FastifyRequest,\n reply: FastifyReply,\n bootstrap?: BuildApiOptions['bootstrap'],\n singleProject?: string,\n): ProjectContext | null {\n const name = projectFromReq(req, singleProject)\n const ctx = registry.get(name)\n if (!ctx) {\n // Issue #340 — registered but still bootstrapping: surface 503 so the\n // probe consumer knows the project is real and incoming, not missing.\n const phase = bootstrap?.status(name)\n if (phase === 'bootstrapping') {\n void reply.code(503).send({ ready: false, project: name, status: 'bootstrapping' })\n return null\n }\n if (phase === 'broken') {\n void reply.code(503).send({ ready: false, project: name, status: 'broken' })\n return null\n }\n void reply.code(404).send({ error: 'project not found', project: name })\n return null\n }\n return ctx\n}\n\nfunction buildLegacyRegistry(opts: BuildApiOptions): Projects {\n if (opts.projects) return opts.projects\n if (!opts.graph) {\n throw new Error('buildApi: either `projects` or `graph` must be provided')\n }\n const registry = new ProjectsClass()\n // pathsForProject only matters here for the snapshot/embeddings paths\n // routes never read; the ingest paths come from explicit options below.\n const paths = pathsForProject(DEFAULT_PROJECT, '')\n registry.set(DEFAULT_PROJECT, {\n graph: opts.graph,\n scanPath: opts.scanPath,\n paths: {\n snapshotPath: paths.snapshotPath,\n errorsPath: opts.errorsPath ?? paths.errorsPath,\n staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,\n embeddingsCachePath: paths.embeddingsCachePath,\n policyViolationsPath: paths.policyViolationsPath,\n },\n searchIndex: opts.searchIndex,\n })\n return registry\n}\n\ninterface RouteContext {\n registry: Projects\n startedAt: number\n // Where the routes are getting mounted. `'root'` is the legacy unprefixed\n // mount that historically resolved every request to the `default` project;\n // `'project'` is the `/projects/:project` plugin scope where the project\n // segment is always present. The /health handler branches on this so the\n // root mount can answer daemon-wide while the scoped mount stays\n // per-project (issue #343).\n scope: 'root' | 'project'\n // Legacy callers passed `errorsPath`/`staleEventsPath` explicitly and\n // expected absent values to disable the read. Track that intent so the\n // /incidents handlers don't accidentally read a phantom file.\n errorsPathFor: (ctx: ProjectContext) => string | undefined\n staleEventsPathFor: (ctx: ProjectContext) => string | undefined\n // policy.json lives at the project root (per ADR-042 §File location), not\n // under neat-out/. Routes that read it map a project context to the path.\n policyFilePathFor: (ctx: ProjectContext) => string | undefined\n // Issue #340 — flips per-project routes from 404 to 503 while a slot is\n // still extracting.\n bootstrap?: BuildApiOptions['bootstrap']\n // ADR-096 §4 — the per-project daemon's one project. When set, unprefixed\n // (and `default`-named) requests resolve to it instead of the `default`\n // project. Absent for the legacy multi-project daemon.\n singleProject?: string\n}\n\n// Registers every project-scoped route on `scope`. Called twice from\n// buildApi: once on the root app (so /graph etc. land at default), once\n// inside a `register(_, { prefix: '/projects/:project' })` plugin so the\n// same handlers run when the URL names a project explicitly.\nfunction registerRoutes(scope: FastifyInstance, ctx: RouteContext): void {\n const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx\n\n // SSE event stream (ADR-051 #1). Dual-mounted: hits /events for default\n // project and /projects/:project/events when scoped. The handler keeps\n // the connection open and writes one frame per bus envelope whose\n // `project` matches.\n scope.get<{ Params: { project?: string } }>('/events', (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n handleSse(req, reply, { project: proj.name })\n })\n\n // Per-project /health stays scoped. The unscoped `/health` at the root\n // mount is handled by the daemon-wide handler below (issue #343) —\n // daemon-wide readiness mustn't pivot on whether the `default` project\n // exists. The scoped variant carries the bootstrap-aware shape from\n // issue #340.\n if (ctx.scope === 'project') {\n scope.get<{ Params: { project?: string } }>('/health', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const uptimeMs = Date.now() - startedAt\n return {\n ok: true,\n project: proj.name,\n uptimeMs,\n // Legacy fields kept additively. The web shell's StatusBar reads\n // nodeCount / edgeCount; ADR-061's HealthResponseSchema validates\n // the canonical triple and lets the extras pass through.\n uptime: Math.floor(uptimeMs / 1000),\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n lastUpdated: new Date().toISOString(),\n }\n })\n }\n\n scope.get<{ Params: { project?: string } }>('/graph', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n return serializeGraph(proj.graph)\n })\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/node/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n return { node: proj.graph.getNodeAttributes(id) as GraphNode }\n },\n )\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/edges/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n const inbound = proj.graph\n .inboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n const outbound = proj.graph\n .outboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n return { inbound, outbound }\n },\n )\n\n // Transitive dependencies (issue #144). BFS outbound to depth N, returning\n // a flat list with distance + edgeType + provenance per dependency.\n // Default depth 3, max 10. The MCP get_dependencies tool calls this.\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { depth?: string }\n }>('/graph/dependencies/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const depth = req.query.depth ? Number(req.query.depth) : TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH\n if (!Number.isFinite(depth) || depth < 1 || depth > TRANSITIVE_DEPENDENCIES_MAX_DEPTH) {\n return reply.code(400).send({\n error: `depth must be an integer in [1, ${TRANSITIVE_DEPENDENCIES_MAX_DEPTH}]`,\n })\n }\n return getTransitiveDependencies(proj.graph, nodeId, depth)\n })\n\n // Observed-only dependencies (issue #578). The runtime CALLS a node makes,\n // file-grained — and, for a ServiceNode, the OBSERVED edges of the files it\n // owns, since the call-site processor lands them on files, not the service\n // root. REST parity for the MCP get_observed_dependencies tool (issue #593);\n // the CLI observed-dependencies verb reads it too.\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/graph/observed-dependencies/:nodeId',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n return getObservedDependencies(proj.graph, nodeId)\n },\n )\n\n // Divergence query — the thesis surface (ADR-060). Read-only, derived,\n // dual-mounted via registerRoutes. Query params filter the result set;\n // body shape is DivergenceResult.\n scope.get<{\n Params: { project?: string }\n Querystring: { type?: string; minConfidence?: string; node?: string }\n }>('/graph/divergences', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n let typeFilter: Set<DivergenceType> | undefined\n if (req.query.type) {\n const candidates = req.query.type\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n const parsed: DivergenceType[] = []\n for (const c of candidates) {\n const r = DivergenceTypeSchema.safeParse(c)\n if (!r.success) {\n return reply.code(400).send({\n error: `unknown divergence type \"${c}\"`,\n allowed: DivergenceTypeSchema.options,\n })\n }\n parsed.push(r.data)\n }\n typeFilter = new Set(parsed)\n }\n let minConfidence: number | undefined\n if (req.query.minConfidence !== undefined) {\n const n = Number(req.query.minConfidence)\n if (!Number.isFinite(n) || n < 0 || n > 1) {\n return reply.code(400).send({\n error: 'minConfidence must be a number in [0, 1]',\n })\n }\n minConfidence = n\n }\n return computeDivergences(proj.graph, {\n ...(typeFilter ? { type: typeFilter } : {}),\n ...(minConfidence !== undefined ? { minConfidence } : {}),\n ...(req.query.node ? { node: req.query.node } : {}),\n })\n })\n\n // ADR-061 envelope rule: list endpoints return { count, total, events }.\n // `total` is the size of the underlying collection; `count` is the size of\n // the slice we're handing back. The web shell counts on both.\n scope.get<{\n Params: { project?: string }\n Querystring: { limit?: string }\n }>('/incidents', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const epath = errorsPathFor(proj)\n if (!epath) return { count: 0, total: 0, events: [] }\n const events = await readErrorEvents(epath)\n const total = events.length\n const limit = req.query.limit ? Number(req.query.limit) : 50\n const safeLimit =\n Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 50\n const sliced = events.slice(0, safeLimit)\n return { count: sliced.length, total, events: sliced }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { limit?: string; edgeType?: string }\n }>('/stale-events', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const spath = staleEventsPathFor(proj)\n if (!spath) return { count: 0, total: 0, events: [] }\n const events = await readStaleEvents(spath)\n const filtered = req.query.edgeType\n ? events.filter((e) => e.edgeType === req.query.edgeType)\n : events\n const ordered = [...filtered].reverse()\n const total = ordered.length\n const limit = req.query.limit ? Number(req.query.limit) : 50\n const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50)\n return { count: sliced.length, total, events: sliced }\n })\n\n // One handler, two paths: `/incidents/:nodeId` is the original REST name and\n // `/graph/incident-history/:nodeId` mirrors the MCP get_incident_history tool\n // so the two surfaces answer under matching names (issue #593).\n const incidentHistoryHandler = async (\n req: FastifyRequest<{ Params: { project?: string; nodeId: string } }>,\n reply: FastifyReply,\n ): Promise<{ count: number; total: number; events: ErrorEvent[] } | undefined> => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n reply.code(404).send({ error: 'node not found', id: nodeId })\n return\n }\n const epath = errorsPathFor(proj)\n if (!epath) return { count: 0, total: 0, events: [] }\n const events = await readErrorEvents(epath)\n const filtered = events.filter(\n (e) =>\n e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, ''),\n )\n return { count: filtered.length, total: filtered.length, events: filtered }\n }\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/incidents/:nodeId',\n incidentHistoryHandler,\n )\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/graph/incident-history/:nodeId',\n incidentHistoryHandler,\n )\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { errorId?: string }\n }>('/graph/root-cause/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n // Load the incident store so root-cause can localize an in-process failure\n // that never crossed a graph edge (#584). When errorId is supplied the\n // named incident colours the reason; the full set is the fallback the graph\n // walk consults when no edge carried an incompatibility.\n const epath = errorsPathFor(proj)\n const incidents = epath ? await readErrorEvents(epath) : []\n let errorEvent: ErrorEvent | undefined\n if (req.query.errorId) {\n errorEvent = incidents.find((e) => e.id === req.query.errorId)\n if (!errorEvent) {\n return reply\n .code(404)\n .send({ error: 'error event not found', id: req.query.errorId })\n }\n }\n const result = getRootCause(proj.graph, nodeId, errorEvent, incidents)\n if (!result) return reply.code(404).send({ error: 'no root cause found', id: nodeId })\n return result\n })\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { depth?: string }\n }>('/graph/blast-radius/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const depth = req.query.depth ? Number(req.query.depth) : undefined\n if (depth !== undefined && (!Number.isFinite(depth) || depth < 0)) {\n return reply.code(400).send({ error: 'depth must be a non-negative number' })\n }\n return getBlastRadius(proj.graph, nodeId, depth)\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { q?: string; limit?: string }\n }>('/search', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const raw = (req.query.q ?? '').trim()\n if (!raw) return reply.code(400).send({ error: 'query parameter `q` is required' })\n const limit = req.query.limit ? Number(req.query.limit) : undefined\n const safeLimit =\n limit !== undefined && Number.isFinite(limit) && limit > 0 ? limit : undefined\n if (proj.searchIndex) {\n const result = await proj.searchIndex.search(raw, safeLimit)\n return {\n query: result.query,\n provider: result.provider,\n matches: result.matches.map((m) => ({ ...m.node, score: m.score })),\n }\n }\n const q = raw.toLowerCase()\n const matches: (GraphNode & { score: number })[] = []\n proj.graph.forEachNode((id, attrs) => {\n const name = (attrs as { name?: string }).name ?? ''\n if (id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {\n matches.push({ ...(attrs as GraphNode), score: 1 })\n }\n })\n return {\n query: q,\n provider: 'substring' as const,\n matches: matches.slice(0, safeLimit),\n }\n })\n\n scope.get<{ Params: { project?: string }; Querystring: { against?: string } }>(\n '/graph/diff',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const against = req.query.against\n if (!against) {\n return reply.code(400).send({ error: 'query parameter `against` is required' })\n }\n try {\n const snapshot = await loadSnapshotForDiff(against)\n return computeGraphDiff(proj.graph, snapshot)\n } catch (err) {\n return reply\n .code(400)\n .send({ error: 'failed to load snapshot', against, detail: (err as Error).message })\n }\n },\n )\n\n // Snapshot push (ADR-074 §1). `neat sync` POSTs a snapshot here; we merge\n // it into the live graph through `mergeSnapshot`, which preserves any\n // accumulated OBSERVED edges per the Rule 2 coexistence contract. Body\n // shape is the same JSON `persist.ts` writes to disk. Dual-mounted at\n // `/snapshot` and `/projects/:project/snapshot` via registerRoutes.\n scope.post<{\n Params: { project?: string }\n Body: { snapshot?: PersistedGraph }\n }>('/snapshot', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const body = req.body\n if (!body || typeof body !== 'object' || !body.snapshot) {\n return reply\n .code(400)\n .send({ error: 'request body must be { snapshot: <persisted-graph> }' })\n }\n const snap = body.snapshot\n if (typeof snap.schemaVersion !== 'number' || snap.schemaVersion !== SCHEMA_VERSION) {\n return reply.code(400).send({\n error: `unsupported snapshot schemaVersion ${snap.schemaVersion} (expected ${SCHEMA_VERSION})`,\n })\n }\n try {\n const result = mergeSnapshot(proj.graph, snap)\n return {\n project: proj.name,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n }\n } catch (err) {\n return reply.code(400).send({\n error: 'snapshot merge failed',\n details: (err as Error).message,\n })\n }\n })\n\n scope.post<{ Params: { project?: string } }>('/graph/scan', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply\n .code(409)\n .send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const result = await extractFromDirectory(proj.graph, proj.scanPath)\n return {\n project: proj.name,\n scanned: proj.scanPath,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n }\n })\n\n // Policy surface (ADR-045 / contract #18). /policies returns the parsed\n // policy.json; /policies/violations is the persistent log; /policies/check\n // is dry-run evaluation. All dual-mounted via registerRoutes.\n scope.get<{ Params: { project?: string } }>('/policies', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const policyPath = ctx.policyFilePathFor(proj)\n if (!policyPath) {\n // No policy file configured for this project — return the empty file\n // shape so consumers don't have to special-case \"no policies yet.\"\n return { version: 1, policies: [] }\n }\n try {\n const policies = await loadPolicyFile(policyPath)\n return { version: 1, policies }\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { severity?: string; policyId?: string }\n }>('/policies/violations', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const log = new PolicyViolationsLog(proj.paths.policyViolationsPath)\n let violations = await log.readAll()\n if (req.query.severity) {\n const sev = PolicySeveritySchema.safeParse(req.query.severity)\n if (!sev.success) {\n return reply.code(400).send({\n error: 'invalid severity',\n details: sev.error.format(),\n })\n }\n violations = violations.filter((v) => v.severity === sev.data)\n }\n if (req.query.policyId) {\n violations = violations.filter((v) => v.policyId === req.query.policyId)\n }\n return { violations }\n })\n\n // Soft guardrail read path (ADR-108 / policies-soft-guardrail.md). Returns\n // the policies APPLICABLE to a node — the rules an agent should be aware of\n // while working there. This INFORMS; it never blocks and carries no verdict.\n // Matching is a direct subject/region match (see selectApplicablePolicies),\n // not the full overlay traversal (ADR-105 §5), which is still unbuilt.\n scope.get<{\n Params: { project?: string }\n Querystring: { node?: string }\n }>('/policies/applicable', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const nodeId = req.query.node\n if (!nodeId) {\n return reply.code(400).send({ error: 'missing required query param \"node\"' })\n }\n const policyPath = ctx.policyFilePathFor(proj)\n let policies: Policy[] = []\n if (policyPath) {\n try {\n policies = await loadPolicyFile(policyPath)\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n }\n const applicable = selectApplicablePolicies(proj.graph, policies, nodeId)\n return { node: nodeId, applicable }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { hypotheticalAction?: unknown }\n }>('/policies/check', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const parsed = PoliciesCheckBodySchema.safeParse(req.body ?? {})\n if (!parsed.success) {\n return reply.code(400).send({\n error: 'invalid /policies/check body',\n details: parsed.error.format(),\n })\n }\n\n const policyPath = ctx.policyFilePathFor(proj)\n let policies: Policy[] = []\n if (policyPath) {\n try {\n policies = await loadPolicyFile(policyPath)\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n }\n\n // No hypothetical → return current violations against the live graph.\n // With a hypothetical → simulate the action against a deep-copy graph\n // (avoids mutation authority concerns), evaluate, return the delta.\n const evalCtx = { now: () => Date.now() }\n if (!parsed.data.hypotheticalAction) {\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return { allowed: blocking.length === 0, violations }\n }\n\n // For now the dry-run simulation re-uses evaluateAllPolicies on the\n // current graph. Full hypothetical simulation (e.g. \"what if I added\n // this OBSERVED edge?\") is the v0.2.4-δ scope; #117 ships the surface,\n // #118 fills in the action shapes' simulation logic.\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return {\n allowed: blocking.length === 0,\n hypotheticalAction: parsed.data.hypotheticalAction,\n violations,\n } as { allowed: boolean; hypotheticalAction: unknown; violations: PolicyViolation[] }\n })\n\n // ── /extend — surgical instrumentation tools (ADR-081, ADR-086) ─────────\n // Six routes. Three read-only (list-uninstrumented, lookup, describe),\n // three operative (apply, dry-run, rollback). File-scope restricted per\n // the extend-skill contract: only instrumentation/otel-init files and\n // package.json. Dual-mounted via registerRoutes.\n\n scope.get<{ Params: { project?: string } }>('/extend/list-uninstrumented', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n try {\n const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath })\n return { libraries: results }\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { library?: string; version?: string }\n }>('/extend/lookup', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { library, version } = req.query\n if (!library) {\n return reply.code(400).send({ error: 'query parameter `library` is required' })\n }\n const result = lookupInstrumentation(library, version)\n if (!result) {\n return reply.code(404).send({ error: 'library not found in registry', library })\n }\n return result\n })\n\n scope.get<{ Params: { project?: string } }>('/extend/describe', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n try {\n const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath })\n return state\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string; instrumentation_package?: string; version?: string; registration_snippet?: string }\n }>('/extend/apply', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library, instrumentation_package, version, registration_snippet } = req.body ?? {}\n if (!library || !instrumentation_package || !version || !registration_snippet) {\n return reply.code(400).send({ error: 'body must include library, instrumentation_package, version, registration_snippet' })\n }\n try {\n const result = await applyExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library, instrumentation_package, version, registration_snippet },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string; instrumentation_package?: string; version?: string; registration_snippet?: string }\n }>('/extend/dry-run', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library, instrumentation_package, version, registration_snippet } = req.body ?? {}\n if (!library || !instrumentation_package || !version || !registration_snippet) {\n return reply.code(400).send({ error: 'body must include library, instrumentation_package, version, registration_snippet' })\n }\n try {\n const result = await dryRunExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library, instrumentation_package, version, registration_snippet },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string }\n }>('/extend/rollback', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library } = req.body ?? {}\n if (!library) {\n return reply.code(400).send({ error: 'body must include library' })\n }\n try {\n const result = await rollbackExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n}\n\nexport async function buildApi(opts: BuildApiOptions): Promise<FastifyInstance> {\n const app = Fastify({ logger: false })\n await app.register(cors, { origin: true })\n\n // ADR-073 §3/§4 — `buildApi` owns auth enforcement so every listener that\n // serves the REST surface gets the same gate, whoever builds it. When a\n // caller doesn't pass auth options explicitly, they resolve from the\n // environment (`NEAT_AUTH_TOKEN` / `NEAT_AUTH_PROXY` / `NEAT_PUBLIC_READ`)\n // through the single `readAuthEnv` reader. A caller that does pass them\n // wins, so the daemon and `serve` keep their explicit wiring; a caller that\n // omits them — `neat watch` — inherits the same token rather than serving\n // open by omission. The bind-host loopback refusal stays with the binding\n // caller via `assertBindAuthority`, which reads the same env.\n const env = readAuthEnv()\n const authToken = opts.authToken ?? env.authToken\n const trustProxy = opts.trustProxy ?? env.trustProxy\n const publicRead = opts.publicRead ?? env.publicRead\n\n // ADR-073 §3 — bearer middleware sits ahead of every route handler. No-op\n // when the resolved token is undefined; loopback-only callers (the laptop\n // dev path) hit that branch. `publicRead` opens GET / HEAD / OPTIONS to\n // anonymous callers while keeping writes gated.\n mountBearerAuth(app, {\n token: authToken,\n trustProxy,\n publicRead,\n })\n\n // ADR-073 §3 amendment — `/api/config` is always unauthenticated. The web\n // shell hits it before any bearer-carrying request to learn which mode the\n // daemon is in. Exposes exactly two booleans — `publicRead` and\n // `authProxy` — and nothing else; no project list, no version, no env.\n app.get('/api/config', async () => ({\n publicRead: publicRead === true,\n authProxy: trustProxy === true,\n }))\n\n const startedAt = opts.startedAt ?? Date.now()\n const registry = buildLegacyRegistry(opts)\n\n const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== undefined\n const legacyStaleExplicit = !opts.projects && opts.staleEventsPath !== undefined\n\n const errorsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyErrorsExplicit ? opts.errorsPath : undefined\n }\n return proj.paths.errorsPath\n }\n const staleEventsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyStaleExplicit ? opts.staleEventsPath : undefined\n }\n return proj.paths.staleEventsPath\n }\n\n // policy.json lives at the project's scanPath root per ADR-042. Without a\n // scanPath we have nowhere to read it from — those projects show as\n // \"no policies configured\" via the empty-file response.\n const policyFilePathFor = (proj: ProjectContext): string | undefined => {\n if (!proj.scanPath) return undefined\n return `${proj.scanPath}/policy.json`\n }\n\n const routeCtx: RouteContext = {\n registry,\n startedAt,\n scope: 'root',\n errorsPathFor,\n staleEventsPathFor,\n policyFilePathFor,\n bootstrap: opts.bootstrap,\n singleProject: opts.singleProject?.name,\n }\n\n // Daemon-wide /health (issue #343). Per ADR-049 the daemon is the unit of\n // readiness; the response answers \"is this process up and what's it\n // currently serving?\" without depending on a `default` project. Probes —\n // orchestrator, kubelet readiness, systemd, supervisors — read this.\n //\n // The `projects` array surfaces every slot the daemon knows about. When\n // the bootstrap tracker is wired (issue #340), the per-entry `status`\n // and `elapsedMs` come from there so the orchestrator's wait loop can\n // tell `bootstrapping` apart from `active` without per-project probes.\n app.get('/health', async () => {\n const uptimeMs = Date.now() - startedAt\n const bootstrapList = opts.bootstrap?.list() ?? []\n const byName = new Map(bootstrapList.map((p) => [p.name, p]))\n const names = new Set<string>([\n ...registry.list(),\n ...bootstrapList.map((p) => p.name),\n ])\n const projects = [...names].sort().map((name) => {\n const proj = registry.get(name)\n const tracked = byName.get(name)\n return {\n name,\n nodeCount: proj?.graph.order ?? 0,\n edgeCount: proj?.graph.size ?? 0,\n ...(tracked ? { status: tracked.status, elapsedMs: tracked.elapsedMs } : {}),\n }\n })\n return {\n ok: true,\n uptimeMs,\n // ADR-096 §7 — a per-project daemon carries its project at the top level\n // so a spawn can confirm a daemon found on a reused port is serving THIS\n // project before reusing it. The legacy multi-project daemon omits it and\n // is matched against the `projects` array instead.\n ...(opts.singleProject ? { project: opts.singleProject.name } : {}),\n projects,\n }\n })\n\n // ADR-096 §4 — \"the daemon is the project.\" A per-project daemon reports only\n // its own project here, so the dashboard pins to the project this daemon\n // serves rather than to whichever machine-registered project happens to be\n // active. The legacy multi-project daemon hands back the machine-level\n // registry (ADR-048) — the one documented bare-array GET. Either way the\n // response is `Array<RegistryEntry>`, which the dashboard and the CLI's\n // bare-verb resolver treat as a list primitive.\n app.get('/projects', async (_req, reply) => {\n if (opts.singleProject) {\n const phase = opts.bootstrap?.status(opts.singleProject.name)\n const entry: RegistryEntry = {\n name: opts.singleProject.name,\n path: opts.singleProject.path,\n registeredAt: new Date(startedAt).toISOString(),\n languages: [],\n // The daemon is serving this project, so it's active unless its\n // bootstrap broke. ('bootstrapping' still reads as active — the project\n // is the one to land on; its graph route answers 503 until ready.)\n status: phase === 'broken' ? 'broken' : 'active',\n }\n return [entry]\n }\n try {\n return await listRegistryProjects()\n } catch (err) {\n return reply.code(500).send({\n error: 'failed to read project registry',\n details: (err as Error).message,\n })\n }\n })\n\n // Singular project lookup (ADR-061 #7). Distinct route from the\n // `/projects/:project/...` dual-mount prefix because Fastify matches on\n // the full path; the trailing-segment-less request lands here.\n app.get<{ Params: { project: string } }>('/projects/:project', async (req, reply) => {\n try {\n const entry = await getRegistryProject(req.params.project)\n if (!entry) {\n return reply\n .code(404)\n .send({ error: 'project not found', project: req.params.project })\n }\n return { project: entry }\n } catch (err) {\n return reply.code(500).send({\n error: 'failed to read project registry',\n details: (err as Error).message,\n })\n }\n })\n\n // Default mount: /graph, /incidents, etc. resolve to project=default.\n // `/health` at this scope is the daemon-wide handler registered above,\n // not the per-project one (issue #343).\n registerRoutes(app, routeCtx)\n\n // Project-scoped mount: same handlers, URL params include `:project`,\n // and `/health` answers per project.\n await app.register(\n async (scope) => {\n registerRoutes(scope, { ...routeCtx, scope: 'project' })\n },\n { prefix: '/projects/:project' },\n )\n\n return app\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { resolve as registryResolve, list as registryList } from '@neat.is/instrumentation-registry'\nimport {\n detectPackageManager,\n runPackageManagerInstall,\n} from '../installers/package-manager.js'\n\nexport interface ExtendContext {\n project: string\n scanPath: string\n}\n\nexport interface LibraryCoverageResult {\n library: string\n coverage: 'bundled' | 'first-party' | 'third-party' | 'http-only' | 'gap'\n installedVersion?: string\n instrumentation_package?: string\n package_version?: string\n registration?: string\n notes?: string\n}\n\nexport interface ProjectInstrumentationState {\n hookFiles: string[]\n envNeat: boolean\n installedDeps: Record<string, string>\n}\n\nexport interface ExtensionApplyResult {\n library: string\n filesTouched: string[]\n depsAdded: string[]\n installOutput: string\n alreadyApplied: boolean\n}\n\nexport interface ExtensionDiff {\n library: string\n filesTouched: string[]\n depsToAdd: string[]\n packageJsonPatch: object\n templatePatch: string\n}\n\ninterface PackageJson {\n name?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n [key: string]: unknown\n}\n\ninterface ExtendLogEntry {\n timestamp: string\n project: string\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n filesTouched: string[]\n depsAdded: string[]\n installOutput: string\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\nasync function readPackageJson(scanPath: string): Promise<PackageJson> {\n const pkgPath = path.join(scanPath, 'package.json')\n const raw = await fs.readFile(pkgPath, 'utf8')\n return JSON.parse(raw) as PackageJson\n}\n\nasync function findHookFiles(scanPath: string): Promise<string[]> {\n const entries = await fs.readdir(scanPath)\n return entries\n .filter(\n (e) =>\n (e.startsWith('instrumentation') || e.startsWith('otel-init')) &&\n /\\.(ts|js)$/.test(e),\n )\n .sort()\n}\n\nfunction extendLogPath(): string {\n return process.env.NEAT_EXTEND_LOG ?? path.join(os.homedir(), '.neat', 'extend-log.ndjson')\n}\n\nasync function appendExtendLog(entry: ExtendLogEntry): Promise<void> {\n const logPath = extendLogPath()\n await fs.mkdir(path.dirname(logPath), { recursive: true })\n await fs.appendFile(logPath, JSON.stringify(entry) + '\\n', 'utf8')\n}\n\n// Insert `snippet` into `fileContent` at the instrumentation array.\n// Tries __INSTRUMENTATION_BLOCK__ first, then last instrumentations.push(,\n// then before new NodeSDK(. Returns null if no insertion point is found.\nfunction splicedContent(fileContent: string, snippet: string): string | null {\n if (fileContent.includes('__INSTRUMENTATION_BLOCK__')) {\n return fileContent.replace('__INSTRUMENTATION_BLOCK__', `${snippet}\\n__INSTRUMENTATION_BLOCK__`)\n }\n\n const lines = fileContent.split('\\n')\n\n let lastPushIdx = -1\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('instrumentations.push(')) lastPushIdx = i\n }\n if (lastPushIdx >= 0) {\n lines.splice(lastPushIdx + 1, 0, snippet)\n return lines.join('\\n')\n }\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('new NodeSDK(')) {\n lines.splice(i, 0, snippet)\n return lines.join('\\n')\n }\n }\n\n return null\n}\n\nexport async function listUninstrumented(ctx: ExtendContext): Promise<LibraryCoverageResult[]> {\n const pkg = await readPackageJson(ctx.scanPath)\n const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n\n const results: LibraryCoverageResult[] = []\n for (const [library, installedVersion] of Object.entries(allDeps)) {\n const entry = registryResolve(library, installedVersion)\n if (!entry) continue\n if (entry.coverage === 'bundled' || entry.coverage === 'http-only') continue\n results.push({\n library,\n coverage: entry.coverage,\n installedVersion,\n instrumentation_package: entry.instrumentation_package,\n package_version: entry.package_version,\n registration: entry.registration,\n notes: entry.notes,\n })\n }\n return results\n}\n\nexport function lookupInstrumentation(\n library: string,\n installedVersion?: string,\n): LibraryCoverageResult | null {\n const entry = registryResolve(library, installedVersion)\n if (!entry) return null\n return {\n library: entry.library,\n coverage: entry.coverage,\n instrumentation_package: entry.instrumentation_package,\n package_version: entry.package_version,\n registration: entry.registration,\n notes: entry.notes,\n }\n}\n\nexport async function describeProjectInstrumentation(\n ctx: ExtendContext,\n): Promise<ProjectInstrumentationState> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n const envNeat = await fileExists(path.join(ctx.scanPath, '.env.neat'))\n\n const registryInstrPackages = new Set<string>(\n registryList()\n .map((e) => e.instrumentation_package)\n .filter((p): p is string => !!p),\n )\n\n const pkg = await readPackageJson(ctx.scanPath)\n const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n\n const installedDeps: Record<string, string> = {}\n for (const [key, version] of Object.entries(allDeps)) {\n if (key.startsWith('@opentelemetry/') || registryInstrPackages.has(key)) {\n installedDeps[key] = version\n }\n }\n\n return { hookFiles, envNeat, installedDeps }\n}\n\nexport async function applyExtension(\n ctx: ExtendContext,\n args: {\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n },\n options?: {\n runInstall?: typeof runPackageManagerInstall\n },\n): Promise<ExtensionApplyResult> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n\n if (hookFiles.length === 0) {\n throw new Error(\n `No instrumentation hook files found in ${ctx.scanPath}. Run \\`neat init\\` first.`,\n )\n }\n\n // Idempotency check: scan all hook files for the snippet\n for (const file of hookFiles) {\n const content = await fs.readFile(path.join(ctx.scanPath, file), 'utf8')\n if (content.includes(args.registration_snippet)) {\n return { library: args.library, filesTouched: [], depsAdded: [], installOutput: '', alreadyApplied: true }\n }\n }\n\n const primaryFile = hookFiles[0]!\n const primaryPath = path.join(ctx.scanPath, primaryFile)\n const filesTouched: string[] = []\n const depsAdded: string[] = []\n\n // 1. Add dep to package.json if not already present\n const pkgPath = path.join(ctx.scanPath, 'package.json')\n const pkg = await readPackageJson(ctx.scanPath)\n if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {\n pkg.dependencies = { ...(pkg.dependencies ?? {}), [args.instrumentation_package]: args.version }\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\\n', 'utf8')\n filesTouched.push('package.json')\n depsAdded.push(`${args.instrumentation_package}@${args.version}`)\n }\n\n // 2. Splice registration snippet into hook file\n const hookContent = await fs.readFile(primaryPath, 'utf8')\n const patched = splicedContent(hookContent, args.registration_snippet)\n if (!patched) {\n throw new Error(\n `Could not find instrumentation insertion point in ${primaryFile}. ` +\n 'Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.',\n )\n }\n await fs.writeFile(primaryPath, patched, 'utf8')\n filesTouched.push(primaryFile)\n\n // 3. Run package manager install\n const cmd = await detectPackageManager(ctx.scanPath)\n const installer = options?.runInstall ?? runPackageManagerInstall\n const install = await installer(cmd)\n const installOutput =\n install.exitCode === 0\n ? `${cmd.pm} install succeeded`\n : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`\n\n // 4. Log the apply\n await appendExtendLog({\n timestamp: new Date().toISOString(),\n project: ctx.project,\n library: args.library,\n instrumentation_package: args.instrumentation_package,\n version: args.version,\n registration_snippet: args.registration_snippet,\n filesTouched,\n depsAdded,\n installOutput,\n })\n\n return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false }\n}\n\nexport async function dryRunExtension(\n ctx: ExtendContext,\n args: {\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n },\n): Promise<ExtensionDiff> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n\n if (hookFiles.length === 0) {\n return {\n library: args.library,\n filesTouched: [],\n depsToAdd: [],\n packageJsonPatch: {},\n templatePatch: \"No hook files found. Run 'neat init' first.\",\n }\n }\n\n for (const file of hookFiles) {\n const content = await fs.readFile(path.join(ctx.scanPath, file), 'utf8')\n if (content.includes(args.registration_snippet)) {\n return {\n library: args.library,\n filesTouched: [],\n depsToAdd: [],\n packageJsonPatch: {},\n templatePatch: 'Already applied — no changes would be made.',\n }\n }\n }\n\n const primaryFile = hookFiles[0]!\n const filesTouched: string[] = []\n const depsToAdd: string[] = []\n let packageJsonPatch: object = {}\n let templatePatch = ''\n\n const pkg = await readPackageJson(ctx.scanPath)\n if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {\n packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } }\n depsToAdd.push(`${args.instrumentation_package}@${args.version}`)\n filesTouched.push('package.json')\n }\n\n const hookContent = await fs.readFile(path.join(ctx.scanPath, primaryFile), 'utf8')\n const patched = splicedContent(hookContent, args.registration_snippet)\n if (patched) {\n filesTouched.push(primaryFile)\n templatePatch = `+ ${args.registration_snippet}`\n } else {\n templatePatch = 'Could not find insertion point in hook file.'\n }\n\n return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch }\n}\n\nexport async function rollbackExtension(\n ctx: ExtendContext,\n args: { library: string },\n): Promise<{ undone: boolean; message: string }> {\n const logPath = extendLogPath()\n\n if (!(await fileExists(logPath))) {\n return { undone: false, message: 'no apply found for library' }\n }\n\n const raw = await fs.readFile(logPath, 'utf8')\n const entries: ExtendLogEntry[] = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => JSON.parse(line) as ExtendLogEntry)\n\n const match = [...entries]\n .reverse()\n .find((e) => e.project === ctx.project && e.library === args.library)\n\n if (!match) {\n return { undone: false, message: 'no apply found for library' }\n }\n\n // Remove dep from package.json\n const pkgPath = path.join(ctx.scanPath, 'package.json')\n if (await fileExists(pkgPath)) {\n const pkg = await readPackageJson(ctx.scanPath)\n if (pkg.dependencies?.[match.instrumentation_package]) {\n const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies\n pkg.dependencies = rest\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\\n', 'utf8')\n }\n }\n\n // Remove the registration snippet from hook files (no re-install — user owns the lockfile)\n const hookFiles = await findHookFiles(ctx.scanPath)\n for (const file of hookFiles) {\n const filePath = path.join(ctx.scanPath, file)\n const content = await fs.readFile(filePath, 'utf8')\n if (content.includes(match.registration_snippet)) {\n const filtered = content\n .split('\\n')\n .filter((line) => !line.includes(match.registration_snippet))\n .join('\\n')\n await fs.writeFile(filePath, filtered, 'utf8')\n break\n }\n }\n\n return {\n undone: true,\n message: `rolled back ${match.library} (${match.instrumentation_package})`,\n }\n}\n","/**\n * Package-manager detection + install invocation.\n *\n * Issue #381 — the apply phase adds dependencies to package.json but\n * relies on the operator to run `npm install` afterwards. The v0.4.5\n * smoke surfaced this as a hard regression on Brief: the installer\n * adds `@opentelemetry/sdk-node` (and `@prisma/instrumentation` when\n * Prisma is in deps) and the next `npm run dev` fails with\n * `Cannot find module '@opentelemetry/sdk-node'` before the OTel SDK\n * even gets a chance to load.\n *\n * ADR-046's \"lockfiles never touched\" rule is about NEAT not directly\n * editing lockfile contents; letting the user's own package manager\n * update them as a side effect of `<pm> install` is consistent with\n * that contract. The clarification is documented in\n * docs/contracts/sdk-install.md.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { spawn } from 'node:child_process'\n\nexport type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm'\n\nexport interface PackageManagerCommand {\n pm: PackageManager\n // The directory the install command runs in. Monorepo workspaces install\n // from the lockfile root, not the per-package directory, so detection\n // walks up from `serviceDir` and reports the lockfile-owning parent.\n cwd: string\n // The argv passed to spawn(). Each entry is one positional token. Selected\n // to keep install output quiet on the happy path; failures still print\n // because stderr is streamed verbatim.\n args: string[]\n}\n\n// Lockfile basename → package manager + the install args we run for it.\n// Priority order — first match wins when multiple lockfiles coexist (rare,\n// but `package-lock.json` alongside `pnpm-lock.yaml` happens during a\n// migration). Bun leads because a project that opted into Bun deliberately\n// rejects npm; pnpm and yarn follow for similar reasons; npm is the default\n// when nothing else applies.\nconst LOCKFILE_PRIORITY: ReadonlyArray<{\n lockfile: string\n pm: PackageManager\n args: string[]\n}> = [\n { lockfile: 'bun.lockb', pm: 'bun', args: ['install', '--no-summary'] },\n { lockfile: 'pnpm-lock.yaml', pm: 'pnpm', args: ['install', '--no-summary'] },\n { lockfile: 'yarn.lock', pm: 'yarn', args: ['install', '--silent'] },\n {\n lockfile: 'package-lock.json',\n pm: 'npm',\n args: ['install', '--no-audit', '--no-fund', '--prefer-offline'],\n },\n]\n\n// Default when no lockfile is present anywhere in the ancestor chain — a\n// fresh project the operator hasn't installed yet. npm is the safe pick:\n// it's available on every Node install and produces a `package-lock.json`\n// the user can later swap out for pnpm/yarn/bun without losing fidelity.\nconst NPM_FALLBACK_ARGS = ['install', '--no-audit', '--no-fund', '--prefer-offline']\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// Resolve the package-manager command for a service directory. Walks up from\n// `serviceDir` looking for a lockfile — the first ancestor that has one\n// owns the install. Monorepos (npm/pnpm/yarn workspaces, Bun workspaces)\n// land their lockfile at the workspace root, so this returns the root cwd\n// even when `serviceDir` is a per-package subdir.\n//\n// No lockfile anywhere → npm at the service dir. A fresh `create-next-app`\n// run sits in this bucket (no install yet, no lockfile to read).\nexport async function detectPackageManager(\n serviceDir: string,\n): Promise<PackageManagerCommand> {\n let dir = path.resolve(serviceDir)\n const stops = new Set<string>()\n for (let i = 0; i < 64; i++) {\n if (stops.has(dir)) break\n stops.add(dir)\n for (const candidate of LOCKFILE_PRIORITY) {\n const lockPath = path.join(dir, candidate.lockfile)\n if (await exists(lockPath)) {\n return { pm: candidate.pm, cwd: dir, args: [...candidate.args] }\n }\n }\n const parent = path.dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n return { pm: 'npm', cwd: path.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] }\n}\n\nexport interface PackageManagerInvocation {\n pm: PackageManager\n cwd: string\n args: string[]\n // 0 → success; non-zero → install failed. Reported in the orchestrator\n // summary so the operator can act before the daemon goes hunting for\n // spans that never arrive.\n exitCode: number\n // Stderr captured from the child process, trimmed. Empty on success.\n // Surfaces the underlying failure cause without dumping the entire\n // install log into the summary.\n stderr: string\n}\n\n// Run the install command and resolve with the outcome. Stdout is dropped\n// (`<pm> install --silent`-style flags already keep it short); stderr is\n// captured so a failure can be relayed up to the orchestrator's summary.\nexport async function runPackageManagerInstall(\n cmd: PackageManagerCommand,\n): Promise<PackageManagerInvocation> {\n return new Promise((resolve) => {\n const child = spawn(cmd.pm, cmd.args, {\n cwd: cmd.cwd,\n // Inherit PATH + HOME so the user's installed managers resolve.\n env: process.env,\n // `false` keeps the parent in control of cleanup if the orchestrator\n // exits before install finishes. Cross-platform-safe.\n shell: false,\n stdio: ['ignore', 'ignore', 'pipe'],\n })\n let stderr = ''\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8')\n })\n child.on('error', (err) => {\n resolve({\n pm: cmd.pm,\n cwd: cmd.cwd,\n args: cmd.args,\n exitCode: 127,\n stderr: stderr + `\\n${err.message}`,\n })\n })\n child.on('close', (code) => {\n resolve({\n pm: cmd.pm,\n cwd: cmd.cwd,\n args: cmd.args,\n exitCode: code ?? 1,\n stderr: stderr.trim(),\n })\n })\n })\n}\n","// SSE handler for the frontend-facing event stream (ADR-051 #1).\n// Subscribes to the bus in events.ts, filters by project, writes\n// `event: <type>\\ndata: <json>\\n\\n` frames to the client. An initial\n// `:open\\n\\n` comment goes out at the handshake so EventSource opens\n// right away instead of waiting on the first event or heartbeat.\n//\n// Backpressure: per-connection queue cap of 1000 outstanding writes; once\n// hit, the connection is dropped with `event: error data: { reason:\n// 'backpressure' }` per ADR-051 #8. Heartbeat: comment line every 30s\n// keeps proxies from idle-timing out (ADR-051 #3).\n\nimport type { FastifyReply, FastifyRequest } from 'fastify'\nimport {\n EVENT_BUS_CHANNEL,\n eventBus,\n type NeatEventEnvelope,\n} from './events.js'\n\nexport const SSE_HEARTBEAT_MS = 30_000\nexport const SSE_BACKPRESSURE_CAP = 1000\n\nexport interface HandleSseOptions {\n project: string\n heartbeatMs?: number\n backpressureCap?: number\n}\n\nexport function handleSse(\n req: FastifyRequest,\n reply: FastifyReply,\n opts: HandleSseOptions,\n): void {\n const heartbeatMs = opts.heartbeatMs ?? SSE_HEARTBEAT_MS\n const backpressureCap = opts.backpressureCap ?? SSE_BACKPRESSURE_CAP\n\n reply.raw.setHeader('Content-Type', 'text/event-stream')\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform')\n reply.raw.setHeader('Connection', 'keep-alive')\n reply.raw.setHeader('X-Accel-Buffering', 'no')\n reply.raw.flushHeaders?.()\n\n // Flushing headers leaves the response body empty, so the browser's\n // EventSource stays in CONNECTING (readyState 0) until the first body byte\n // lands — which, on a quiet graph, is the first real event or the 30s\n // heartbeat, whichever comes first. Write a comment line right away so the\n // stream opens at the handshake and EventSource fires onopen immediately.\n // A colon-prefixed comment is a no-op per the SSE spec (clients ignore it,\n // same as the heartbeat), so it sits outside the locked ADR-051 taxonomy.\n // Written raw, bypassing the backpressure accounting below, exactly like\n // the heartbeat does.\n reply.raw.write(':open\\n\\n')\n\n let pending = 0\n let dropped = false\n\n const closeConnection = (): void => {\n if (dropped) return\n dropped = true\n eventBus.off(EVENT_BUS_CHANNEL, listener)\n clearInterval(heartbeat)\n if (!reply.raw.writableEnded) reply.raw.end()\n }\n\n const writeFrame = (frame: string): void => {\n if (dropped) return\n if (pending >= backpressureCap) {\n // Past the cap — emit one final error frame and drop. Don't try to\n // gracefully drain; a slow consumer that's already 1000 frames behind\n // is not going to catch up.\n const errFrame = `event: error\\ndata: ${JSON.stringify({ reason: 'backpressure' })}\\n\\n`\n reply.raw.write(errFrame)\n closeConnection()\n return\n }\n pending++\n reply.raw.write(frame, () => {\n pending = Math.max(0, pending - 1)\n })\n }\n\n const listener = (envelope: NeatEventEnvelope): void => {\n if (envelope.project !== opts.project) return\n writeFrame(`event: ${envelope.type}\\ndata: ${JSON.stringify(envelope.payload)}\\n\\n`)\n }\n\n eventBus.on(EVENT_BUS_CHANNEL, listener)\n\n const heartbeat = setInterval(() => {\n if (dropped) return\n reply.raw.write(':heartbeat\\n\\n')\n }, heartbeatMs)\n if (typeof heartbeat.unref === 'function') heartbeat.unref()\n\n req.raw.on('close', closeConnection)\n reply.raw.on('close', closeConnection)\n reply.raw.on('error', closeConnection)\n}\n"],"mappings":";;;;;;AAAA,OAAO,kBAAkB;AAWzB,IAAM,qBACJ,aACA;AAMK,IAAM,kBAAkB;AAK/B,IAAM,SAAS,oBAAI,IAAuB;AAE1C,SAAS,YAAuB;AAC9B,SAAO,IAAI,mBAAyC,EAAE,gBAAgB,MAAM,CAAC;AAC/E;AAEO,SAAS,SAAS,UAAkB,iBAA4B;AACrE,MAAI,IAAI,OAAO,IAAI,OAAO;AAC1B,MAAI,CAAC,GAAG;AACN,QAAI,UAAU;AACd,WAAO,IAAI,SAAS,CAAC;AAAA,EACvB;AACA,SAAO;AACT;AAYO,SAAS,WAAW,SAAwB;AACjD,MAAI,YAAY,QAAW;AACzB,WAAO,MAAM;AACb;AAAA,EACF;AACA,SAAO,OAAO,OAAO;AACvB;;;ACvDA,SAAS,YAAY,UAAU;AAC/B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,YAAY;;;ACHnB;AAAA,EACE,OAAS;AAAA,IACP;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,QAAU;AAAA,MACV,QAAU;AAAA,MACV,kBAAoB;AAAA,MACpB,kBAAoB;AAAA,MACpB,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,uBAAyB;AAAA,IACvB;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,gBAAkB;AAAA,MAClB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,gBAAkB;AAAA,MAClB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,gBAAkB;AAAA,MAClB,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,kBAAoB;AAAA,IAClB;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,UAAY;AAAA,QACV,MAAQ;AAAA,QACR,YAAc;AAAA,MAChB;AAAA,MACA,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,UAAY;AAAA,QACV,MAAQ;AAAA,QACR,YAAc;AAAA,MAChB;AAAA,MACA,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,UAAY;AAAA,QACV,MAAQ;AAAA,QACR,YAAc;AAAA,MAChB;AAAA,MACA,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,gBAAkB;AAAA,IAChB;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,mBAAqB;AAAA,MACrB,QAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,QAAU;AAAA,IACZ;AAAA,EACF;AACF;;;ADlEA,IAAM,gBAAgB;AACtB,IAAI,eAAoC;AACxC,IAAI,sBAAsB;AAE1B,IAAM,mBAAmB,KAAK,KAAK,GAAG,QAAQ,GAAG,OAAO;AACxD,IAAM,oBAAoB,KAAK,KAAK,kBAAkB,mBAAmB;AACzE,IAAM,gBAAgB,KAAK,KAAK,KAAK;AAWrC,SAAS,qBAAqB,eAAuB,WAA4B;AAC/E,QAAM,IAAI,SAAS,eAAe,EAAE;AACpC,QAAM,IAAI,SAAS,WAAW,EAAE;AAChC,MAAI,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,EAAG,QAAO,KAAK;AAE1D,QAAM,KAAK,OAAO,OAAO,aAAa;AACtC,QAAM,KAAK,OAAO,OAAO,SAAS;AAClC,MAAI,MAAM,GAAI,QAAO,OAAO,IAAI,IAAI,EAAE;AAEtC,SAAO;AACT;AAEO,SAAS,mBACd,QACA,eACA,QACA,eACqB;AACrB,QAAM,SAAS,cAAc;AAC7B,QAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,UAAU,EAAE,WAAW,MAAM;AAChF,MAAI,CAAC,KAAM,QAAO,EAAE,YAAY,KAAK;AAErC,MAAI,KAAK,oBAAoB,CAAC,qBAAqB,eAAe,KAAK,gBAAgB,GAAG;AACxF,WAAO,EAAE,YAAY,KAAK;AAAA,EAC5B;AAEA,QAAM,gBAAgB,OAAO,OAAO,aAAa;AACjD,MAAI,CAAC,cAAe,QAAO,EAAE,YAAY,KAAK;AAE9C,MAAI,OAAO,GAAG,eAAe,KAAK,gBAAgB,GAAG;AACnD,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,kBAAkB,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,KAAK;AAC5B;AAaA,SAAS,mBAAmB,kBAA0B,qBAAsC;AAC1F,MAAI;AACF,UAAM,WAAW,OAAO,OAAO,mBAAmB;AAClD,QAAI,CAAC,SAAU,QAAO;AAKtB,WAAO,OAAO,OAAO,kBAAkB,KAAK,SAAS,OAAO,IAAI;AAAA,MAC9D,mBAAmB;AAAA,IACrB,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,0BACd,YACA,wBACA,kBACiB;AACjB,MAAI,WAAW,qBAAqB,wBAAwB;AAC1D,UAAM,IAAI,OAAO,OAAO,sBAAsB;AAC9C,QAAI,KAAK,OAAO,GAAG,GAAG,WAAW,iBAAiB,GAAG;AACnD,aAAO,EAAE,YAAY,KAAK;AAAA,IAC5B;AAAA,EACF;AACA,MAAI,CAAC,kBAAkB;AACrB,WAAO,EAAE,YAAY,KAAK;AAAA,EAC5B;AACA,MAAI,mBAAmB,kBAAkB,WAAW,cAAc,GAAG;AACnE,WAAO,EAAE,YAAY,KAAK;AAAA,EAC5B;AACA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ,WAAW;AAAA,IACnB,qBAAqB,WAAW;AAAA,EAClC;AACF;AASO,SAAS,qBACd,UACA,wBACA,yBACsB;AACtB,MAAI,CAAC,uBAAwB,QAAO,EAAE,YAAY,KAAK;AACvD,MAAI,SAAS,mBAAmB;AAC9B,UAAM,IAAI,OAAO,OAAO,sBAAsB;AAC9C,QAAI,KAAK,OAAO,GAAG,GAAG,SAAS,iBAAiB,GAAG;AACjD,aAAO,EAAE,YAAY,KAAK;AAAA,IAC5B;AAAA,EACF;AACA,MAAI,CAAC,yBAAyB;AAC5B,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ,SAAS;AAAA,MACjB,UAAU,SAAS;AAAA,IACrB;AAAA,EACF;AACA,QAAM,kBAAkB,OAAO,OAAO,uBAAuB;AAC7D,MAAI,CAAC,gBAAiB,QAAO,EAAE,YAAY,KAAK;AAChD,MAAI,OAAO,GAAG,iBAAiB,SAAS,SAAS,UAAU,GAAG;AAC5D,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ,SAAS;AAAA,MACjB,UAAU,SAAS;AAAA,MACnB,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO,EAAE,YAAY,KAAK;AAC5B;AAEO,SAAS,mBACd,MACA,iBAC0C;AAC1C,MAAI,oBAAoB,OAAW,QAAO,EAAE,YAAY,KAAK;AAC7D,MAAI,KAAK,mBAAmB;AAC1B,UAAM,IAAI,OAAO,OAAO,eAAe;AACvC,UAAM,MAAM,OAAO,OAAO,KAAK,iBAAiB;AAChD,QAAI,KAAK,OAAO,OAAO,GAAG,GAAG,GAAG,EAAG,QAAO,EAAE,YAAY,KAAK;AAAA,EAC/D;AACA,SAAO,EAAE,YAAY,OAAO,QAAQ,KAAK,OAAO;AAClD;AAEA,SAAS,gBAA8B;AACrC,SAAO,gBAAgB;AACzB;AAEA,SAAS,cAAc,GAAiB,GAA+B;AACrE,SAAO;AAAA,IACL,OAAO,CAAC,GAAG,EAAE,OAAO,GAAI,EAAE,SAAS,CAAC,CAAE;AAAA,IACtC,uBAAuB;AAAA,MACrB,GAAI,EAAE,yBAAyB,CAAC;AAAA,MAChC,GAAI,EAAE,yBAAyB,CAAC;AAAA,IAClC;AAAA,IACA,kBAAkB,CAAC,GAAI,EAAE,oBAAoB,CAAC,GAAI,GAAI,EAAE,oBAAoB,CAAC,CAAE;AAAA,IAC/E,gBAAgB,CAAC,GAAI,EAAE,kBAAkB,CAAC,GAAI,GAAI,EAAE,kBAAkB,CAAC,CAAE;AAAA,EAC3E;AACF;AAEA,eAAe,gBAAgB,KAA2C;AACxE,MAAI;AACF,UAAM,MAAM,MAAM,GAAG,SAAS,mBAAmB,MAAM;AACvD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,QAAQ,IAAK,QAAO;AAC/B,UAAM,MAAM,KAAK,IAAI,IAAI,IAAI,KAAK,OAAO,SAAS,EAAE,QAAQ;AAC5D,QAAI,MAAM,cAAe,QAAO;AAChC,WAAO,OAAO;AAAA,EAChB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,iBAAiB,KAAa,QAAqC;AAChF,QAAM,OAAwB;AAAA,IAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,EACF;AACA,MAAI;AACF,UAAM,GAAG,MAAM,kBAAkB,EAAE,WAAW,KAAK,CAAC;AACpD,UAAM,GAAG,UAAU,mBAAmB,KAAK,UAAU,IAAI,GAAG,MAAM;AAAA,EACpE,SAAS,KAAK;AACZ,YAAQ,KAAK,yCAA0C,IAAc,OAAO,EAAE;AAAA,EAChF;AACF;AAWA,eAAsB,qBAA4C;AAChE,MAAI,aAAc,QAAO;AACzB,MAAI,qBAAqB;AACvB,mBAAe;AACf,WAAO;AAAA,EACT;AACA,wBAAsB;AAEtB,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,KAAK;AACR,mBAAe;AACf,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM,gBAAgB,GAAG;AACxC,MAAI,QAAQ;AACV,mBAAe,cAAc,eAAe,MAAM;AAClD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,GAAG,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAC9D,UAAM,SAAU,MAAM,IAAI,KAAK;AAC/B,UAAM,iBAAiB,KAAK,MAAM;AAClC,mBAAe,cAAc,eAAe,MAAM;AAClD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ;AAAA,MACN,wCAAyC,IAAc,OAAO;AAAA,IAChE;AACA,mBAAe;AACf,WAAO;AAAA,EACT;AACF;AASO,SAAS,cAAqC;AACnD,SAAO,cAAc,EAAE;AACzB;AAEO,SAAS,wBAAyD;AACvE,SAAO,cAAc,EAAE,yBAAyB,CAAC;AACnD;AAEO,SAAS,mBAA+C;AAC7D,SAAO,cAAc,EAAE,oBAAoB,CAAC;AAC9C;AAEO,SAAS,iBAA2C;AACzD,SAAO,cAAc,EAAE,kBAAkB,CAAC;AAC5C;;;AErTA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAwBP,IAAM,uBAAuB;AAC7B,IAAM,6BAA6B;AAEnC,SAAS,eAAe,OAAkB,QAAyB;AACjE,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,SAAO,MAAM,SAAS,SAAS;AACjC;AASA,SAAS,qBACP,OACA,QACyC;AACzC,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,MAAI,MAAM,SAAS,SAAS,aAAa;AACvC,WAAO,EAAE,IAAI,QAAQ,KAAK,MAAqB;AAAA,EACjD;AACA,MAAI,MAAM,SAAS,SAAS,UAAU;AACpC,eAAW,UAAU,MAAM,aAAa,MAAM,GAAG;AAC/C,YAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,UAAI,EAAE,SAAS,SAAS,SAAU;AAClC,YAAM,QAAQ,MAAM,kBAAkB,EAAE,MAAM;AAC9C,UAAI,MAAM,SAAS,SAAS,aAAa;AACvC,eAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,MAAqB;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQA,SAAS,iBAAiB,OAAkB,SAA2C;AACrF,QAAM,OAAO,oBAAI,IAAuB;AACxC,aAAW,MAAM,SAAS;AACxB,UAAM,IAAI,MAAM,kBAAkB,EAAE;AACpC,QAAI,eAAe,OAAO,EAAE,MAAM,EAAG;AACrC,UAAM,MAAM,KAAK,IAAI,EAAE,MAAM;AAC7B,QAAI,CAAC,OAAO,UAAU,EAAE,UAAU,IAAI,UAAU,IAAI,UAAU,GAAG;AAC/D,WAAK,IAAI,EAAE,QAAQ,CAAC;AAAA,IACtB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAkB,SAA2C;AACrF,QAAM,OAAO,oBAAI,IAAuB;AACxC,aAAW,MAAM,SAAS;AACxB,UAAM,IAAI,MAAM,kBAAkB,EAAE;AACpC,QAAI,eAAe,OAAO,EAAE,MAAM,EAAG;AACrC,UAAM,MAAM,KAAK,IAAI,EAAE,MAAM;AAC7B,QAAI,CAAC,OAAO,UAAU,EAAE,UAAU,IAAI,UAAU,IAAI,UAAU,GAAG;AAC/D,WAAK,IAAI,EAAE,QAAQ,CAAC;AAAA,IACtB;AAAA,EACF;AACA,SAAO;AACT;AAWA,IAAM,qBAA6C;AAAA,EACjD,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,OAAO;AACT;AAEA,SAAS,aAAa,WAAuC;AAC3D,MAAI,CAAC,aAAa,aAAa,EAAG,QAAO;AAEzC,QAAM,IAAI,MAAM,KAAK,MAAM,YAAY,CAAC,IAAI;AAC5C,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAEA,SAAS,cAAc,OAAmC;AACxD,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,OAAO,KAAK,KAAK;AACvB,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,SAAS,KAAK,MAAM;AACtB,UAAM,KAAK,QAAQ,SAAS,KAAK;AACjC,WAAO,IAAM,MAAM;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,WAA+B,YAAwC;AAChG,MAAI,CAAC,aAAa,aAAa,EAAG,QAAO;AACzC,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,QAAQ,IAAK,QAAO;AACxB,SAAO,IAAI,OAAO;AACpB;AAEO,SAAS,kBAAkB,MAAiB,MAAM,KAAK,IAAI,GAAW;AAC3E,QAAM,UAAU,mBAAmB,KAAK,UAAU,KAAK;AAMvD,QAAM,YAAY,KAAK,QAAQ,aAAa,KAAK;AACjD,QAAM,QAAQ,KAAK,QAAQ,qBAAqB,gBAAgB,MAAM,GAAG;AACzE,MAAI,cAAc,UAAa,UAAU,UAAa,KAAK,WAAW,QAAW;AAC/E,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,aAAa,SAAS;AAChC,QAAM,IAAI,cAAc,KAAK;AAC7B,QAAM,IAAI,kBAAkB,WAAW,KAAK,QAAQ,UAAU;AAC9D,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,IAAI,IAAI,CAAC,CAAC;AACrD;AAEA,SAAS,gBAAgB,MAAiB,KAAiC;AACzE,MAAI,CAAC,KAAK,aAAc,QAAO;AAC/B,QAAM,IAAI,KAAK,MAAM,KAAK,YAAY;AACtC,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,GAAG,MAAM,CAAC;AAC5B;AAQA,SAAS,kBAAkB,OAAoB,MAAM,KAAK,IAAI,GAAW;AACvE,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,UAAU;AACd,aAAW,KAAK,OAAO;AACrB,eAAW,kBAAkB,GAAG,GAAG;AAAA,EACrC;AACA,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC;AACzC;AAUA,SAAS,oBAAoB,OAAkB,OAAe,UAAwB;AACpF,MAAI,OAAa,EAAE,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,EAAE;AAC5C,QAAM,UAAU,oBAAI,IAAY,CAAC,KAAK,CAAC;AAEvC,WAAS,KAAK,MAAcA,QAAgB,OAA0B;AACpE,QAAIA,OAAK,SAAS,KAAK,KAAK,QAAQ;AAClC,aAAO,EAAE,MAAM,CAAC,GAAGA,MAAI,GAAG,OAAO,CAAC,GAAG,KAAK,EAAE;AAAA,IAC9C;AACA,QAAIA,OAAK,SAAS,KAAK,SAAU;AAEjC,UAAM,WAAW,iBAAiB,OAAO,MAAM,aAAa,IAAI,CAAC;AACjE,eAAW,CAAC,OAAO,IAAI,KAAK,UAAU;AACpC,UAAI,QAAQ,IAAI,KAAK,EAAG;AACxB,cAAQ,IAAI,KAAK;AACjB,MAAAA,OAAK,KAAK,KAAK;AACf,YAAM,KAAK,IAAI;AACf,WAAK,OAAOA,QAAM,KAAK;AACvB,MAAAA,OAAK,IAAI;AACT,YAAM,IAAI;AACV,cAAQ,OAAO,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,OAAK,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;AACvB,SAAO;AACT;AAsBA,SAAS,uBACP,OACA,QACAC,OACuB;AACvB,QAAM,WAAW;AAGjB,QAAM,iBAAiB,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,MAAM;AAC/E,MAAI,eAAe,WAAW,EAAG,QAAO;AAExC,aAAW,MAAMA,MAAK,MAAM;AAM1B,UAAM,QAAQ,qBAAqB,OAAO,EAAE;AAC5C,QAAI,CAAC,MAAO;AACZ,UAAM,EAAE,IAAIC,YAAW,IAAI,IAAI;AAC/B,UAAM,OAAO,IAAI,gBAAgB,CAAC;AAClC,eAAW,QAAQ,gBAAgB;AACjC,YAAM,WAAW,KAAK,KAAK,MAAM;AACjC,UAAI,CAAC,SAAU;AACf,YAAM,SAAS;AAAA,QACb,KAAK;AAAA,QACL;AAAA,QACA,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AACA,UAAI,CAAC,OAAO,YAAY;AACtB,eAAO;AAAA,UACL,eAAeA;AAAA,UACf,iBAAiB,OAAO,UAAU;AAAA,UAClC,GAAI,OAAO,mBACP;AAAA,YACE,mBAAmB,WAAW,IAAI,IAAI,IAAI,KAAK,MAAM,iBAAiB,OAAO,gBAAgB;AAAA,UAC/F,IACA,CAAC;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,sBACP,OACA,SACAD,OACuB;AACvB,aAAW,MAAMA,MAAK,MAAM;AAI1B,UAAM,QAAQ,qBAAqB,OAAO,EAAE;AAC5C,QAAI,CAAC,MAAO;AACZ,UAAM,EAAE,IAAIC,YAAW,IAAI,IAAI;AAC/B,UAAM,OAAO,IAAI,gBAAgB,CAAC;AAClC,UAAM,oBAAoB,IAAI;AAE9B,eAAW,cAAc,sBAAsB,GAAG;AAChD,YAAM,WAAW,KAAK,WAAW,OAAO;AACxC,UAAI,CAAC,SAAU;AACf,YAAM,SAAS,0BAA0B,YAAY,UAAU,iBAAiB;AAChF,UAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,eAAO;AAAA,UACL,eAAeA;AAAA,UACf,iBAAiB,OAAO;AAAA,UACxB,GAAI,OAAO,sBACP;AAAA,YACE,mBAAmB,QAAQ,IAAI,IAAI,yBAAyB,OAAO,mBAAmB;AAAA,UACxF,IACA,CAAC;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAEA,eAAW,YAAY,iBAAiB,GAAG;AACzC,YAAM,WAAW,KAAK,SAAS,OAAO;AACtC,UAAI,CAAC,SAAU;AACf,YAAM,mBAAmB,KAAK,SAAS,SAAS,IAAI;AACpD,YAAM,SAAS,qBAAqB,UAAU,UAAU,gBAAgB;AACxE,UAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,eAAO;AAAA,UACL,eAAeA;AAAA,UACf,iBAAiB,OAAO;AAAA,UACxB,mBAAmB,WAAW,IAAI,IAAI,MAAM,SAAS,SAAS,IAAI,UAAU,SAAS,SAAS,UAAU;AAAA,QAC1G;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQA,SAAS,mBACP,OACA,QACAD,OACuB;AACvB,QAAM,QAAQ,qBAAqB,OAAO,OAAO,EAAE;AACnD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,sBAAsB,OAAO,MAAM,KAAKA,KAAI;AACrD;AAMA,IAAM,kBAAsE;AAAA,EAC1E,CAAC,SAAS,YAAY,GAAG;AAAA,EACzB,CAAC,SAAS,WAAW,GAAG;AAAA,EACxB,CAAC,SAAS,QAAQ,GAAG;AACvB;AAEO,SAAS,aACd,OACA,aACA,YACA,WACwB;AACxB,MAAI,CAAC,MAAM,QAAQ,WAAW,EAAG,QAAO;AACxC,QAAM,SAAS,MAAM,kBAAkB,WAAW;AAClD,QAAM,QAAQ,gBAAgB,OAAO,IAAI;AAEzC,MAAI,OAAO;AACT,UAAMA,QAAO,oBAAoB,OAAO,aAAa,oBAAoB;AACzE,UAAM,QAAQ,MAAM,OAAO,QAAQA,KAAI;AACvC,QAAI,OAAO;AACT,YAAM,SAAS,aACX,GAAG,MAAM,eAAe,qBAAqB,WAAW,YAAY,MACpE,MAAM;AAKV,aAAO,sBAAsB,MAAM;AAAA,QACjC,eAAe,MAAM;AAAA,QACrB,iBAAiB;AAAA,QACjB,eAAeA,MAAK;AAAA,QACpB,iBAAiBA,MAAK,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,QACnD,YAAY,kBAAkBA,MAAK,KAAK;AAAA,QACxC,mBAAmB,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AASA,MAAI,OAAO,SAAS,SAAS,aAAa;AACxC,UAAM,eAAe,sBAAsB,OAAO,aAAa,WAAW,UAAU;AACpF,QAAI,aAAc,QAAO;AAAA,EAC3B;AASA,SAAO,uBAAuB,aAAa,WAAW,UAAU;AAClE;AAMA,IAAM,iCAAiC;AAMvC,SAAS,oBAAoB,IAAgB,QAAyB;AACpE,SAAO,GAAG,iBAAiB,UAAU,GAAG,YAAY,OAAO,QAAQ,aAAa,EAAE;AACpF;AAmBA,SAAS,sBACP,QACA,WACA,YAC6B;AAC7B,QAAM,OAAO,aAAa,UAAU,SAAS,IAAI,YAAY,aAAa,CAAC,UAAU,IAAI,CAAC;AAC1F,QAAM,WAAW,KAAK,OAAO,CAAC,OAAO,oBAAoB,IAAI,MAAM,CAAC;AACpE,MAAI,SAAS,WAAW,EAAG,QAAO;AAGlC,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC,EAAE,CAAC;AACrF,QAAM,QAAQ,OAAO,cAAc,CAAC;AACpC,QAAM,WAAW,OAAO,MAAM,eAAe,MAAM,WAAW,MAAM,eAAe,IAAI;AACvF,QAAM,SAAS,OAAO,MAAM,aAAa,MAAM,WAAW,MAAM,aAAa,IAAI;AACjF,QAAM,QAAQ,OAAO,MAAM,YAAY,MAAM,WAAW,MAAM,YAAY,IAAI;AAC9E,QAAM,WAAW,WAAW,GAAG,QAAQ,GAAG,WAAW,SAAY,IAAI,MAAM,KAAK,EAAE,KAAK;AAQvF,QAAM,WAAW,SAAS,OAAO,CAAC,OAAO,GAAG,iBAAiB,OAAO,YAAY;AAChF,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO,QAAQ,IAAI,KAAK,KAAK,yBAAyB;AAC5D,QAAM,cAAc,CAAC,GAAG,OAAO,OAAO,KAAK,OAAO,YAAY,EAAE;AAChE,MAAI,SAAU,aAAY,KAAK,eAAe,QAAQ,EAAE;AACxD,QAAM,kBAAkB,GAAG,YAAY,KAAK,UAAK,CAAC,GAAG,IAAI;AAMzD,QAAM,kBAAkB,OAAO,iBAAiB,UAAU,OAAO,aAAa,WAAW,OAAO;AAChG,QAAM,WAAW,kBAAkB,OAAO,eAAe;AAEzD,QAAM,oBAAoB,WACtB,WAAW,QAAQ,GAAG,QAAQ,aAAa,KAAK,KAAK,EAAE,KACvD,QACE,WAAW,OAAO,OAAO,kBAAkB,KAAK,KAChD;AAEN,SAAO;AAAA,IACL,eAAe,YAAY;AAAA,IAC3B;AAAA,IACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;AAAA,EACnD;AACF;AAMA,SAAS,uBACP,QACA,WACA,YACwB;AACxB,QAAM,MAAM,sBAAsB,QAAQ,WAAW,UAAU;AAC/D,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,gBAAgB,IAAI,WAAW,CAAC,QAAQ,IAAI,QAAQ,IAAI,CAAC,MAAM;AACrE,QAAM,kBAAkB,IAAI,WAAW,CAAC,WAAW,QAAQ,IAAI,CAAC;AAEhE,SAAO,sBAAsB,MAAM;AAAA,IACjC,eAAe,IAAI;AAAA,IACnB,iBAAiB,IAAI;AAAA,IACrB;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,GAAI,IAAI,oBAAoB,EAAE,mBAAmB,IAAI,kBAAkB,IAAI,CAAC;AAAA,EAC9E,CAAC;AACH;AAKA,SAAS,kBAAkB,GAAuB;AAChD,SAAO,EAAE,SAAS,SAAS,UAAU,EAAE,QAAQ,cAAc,KAAK;AACpE;AAOA,SAAS,sBAAsB,OAAkBC,YAA6B;AAC5E,QAAM,MAAM,CAACA,UAAS;AACtB,aAAW,UAAU,MAAM,cAAcA,UAAS,GAAG;AACnD,UAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,QAAI,EAAE,SAAS,SAAS,SAAU;AAClC,UAAM,MAAM,MAAM,kBAAkB,EAAE,MAAM;AAC5C,QAAI,IAAI,SAAS,SAAS,SAAU,KAAI,KAAK,EAAE,MAAM;AAAA,EACvD;AACA,SAAO;AACT;AAIA,SAAS,qBACP,GACA,IACA,SACA,OACS;AACT,QAAM,KAAK,EAAE,QAAQ,cAAc;AACnC,QAAM,KAAK,QAAQ,QAAQ,cAAc;AACzC,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,MAAI,UAAU,EAAE,UAAU,MAAM,UAAU,QAAQ,UAAU,GAAG;AAC7D,WAAO,UAAU,EAAE,UAAU,IAAI,UAAU,QAAQ,UAAU;AAAA,EAC/D;AACA,SAAO,KAAK;AACd;AAOA,SAAS,oBACP,OACAA,YACA,SACiD;AACjD,MAAI,OAAwD;AAC5D,aAAW,OAAO,sBAAsB,OAAOA,UAAS,GAAG;AACzD,eAAW,UAAU,MAAM,cAAc,GAAG,GAAG;AAC7C,YAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,UAAI,CAAC,kBAAkB,CAAC,EAAG;AAC3B,UAAI,eAAe,OAAO,EAAE,MAAM,EAAG;AACrC,YAAM,QAAQ,qBAAqB,OAAO,EAAE,MAAM;AAClD,UAAI,CAAC,SAAS,QAAQ,IAAI,MAAM,EAAE,EAAG;AACrC,UAAI,CAAC,QAAQ,qBAAqB,GAAG,MAAM,IAAI,KAAK,MAAM,KAAK,WAAW,GAAG;AAC3E,eAAO,EAAE,aAAa,MAAM,IAAI,MAAM,EAAE;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,uBACP,OACA,iBACA,UACgE;AAChE,QAAMF,SAAO,CAAC,eAAe;AAC7B,QAAM,QAAqB,CAAC;AAC5B,QAAM,UAAU,oBAAI,IAAY,CAAC,eAAe,CAAC;AACjD,MAAI,UAAU;AAEd,WAAS,QAAQ,GAAG,QAAQ,UAAU,SAAS;AAC7C,UAAM,MAAM,oBAAoB,OAAO,SAAS,OAAO;AACvD,QAAI,CAAC,IAAK;AACV,IAAAA,OAAK,KAAK,IAAI,WAAW;AACzB,UAAM,KAAK,IAAI,IAAI;AACnB,YAAQ,IAAI,IAAI,WAAW;AAC3B,cAAU,IAAI;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,EAAE,MAAAA,QAAM,OAAO,SAAS,QAAQ;AACzC;AAOA,SAAS,sBACP,OACA,UACA,WACA,YACwB;AACxB,QAAM,QAAQ,uBAAuB,OAAO,UAAU,oBAAoB;AAC1E,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,UAAU,MAAM;AACtB,QAAMA,SAAO,CAAC,GAAG,MAAM,IAAI;AAC3B,QAAM,kBAAkB,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU;AAI3D,QAAM,iBAAiB,kBAAkB,MAAM,KAAK;AACpD,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,iBAAiB,8BAA8B,CAAC;AAE3F,QAAM,MAAM,sBAAsB,SAAS,WAAW,UAAU;AAChE,MAAI,KAAK;AACP,QAAI,gBAAgB;AACpB,QAAI,IAAI,UAAU;AAChB,MAAAA,OAAK,KAAK,IAAI,QAAQ;AACtB,sBAAgB,KAAK,WAAW,QAAQ;AACxC,sBAAgB,IAAI;AAAA,IACtB;AACA,WAAO,sBAAsB,MAAM;AAAA,MACjC;AAAA,MACA,iBAAiB,IAAI;AAAA,MACrB,eAAeA;AAAA,MACf;AAAA,MACA;AAAA,MACA,GAAI,IAAI,oBAAoB,EAAE,mBAAmB,IAAI,kBAAkB,IAAI,CAAC;AAAA,IAC9E,CAAC;AAAA,EACH;AAIA,QAAM,WAAW,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC;AACnD,QAAM,OAAO,SAAS,QAAQ,cAAc;AAC5C,QAAM,cAAc,QAAQ,QAAQ,aAAa,EAAE;AACnD,SAAO,sBAAsB,MAAM;AAAA,IACjC,eAAe;AAAA,IACf,iBAAiB,GAAG,WAAW,iCAAiC,IAAI,kBAAkB,SAAS,IAAI,KAAK,GAAG;AAAA,IAC3G,eAAeA;AAAA,IACf;AAAA,IACA;AAAA,IACA,mBAAmB,WAAW,WAAW;AAAA,EAC3C,CAAC;AACH;AAaO,SAAS,eACd,OACA,QACA,WAAW,4BACQ;AACnB,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,wBAAwB,MAAM,EAAE,QAAQ,QAAQ,eAAe,CAAC,GAAG,eAAe,EAAE,CAAC;AAAA,EAC9F;AAaA,QAAM,OAAO,oBAAI,IAAqC;AACtD,QAAM,QAAiB,CAAC,EAAE,QAAQ,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC;AAC9E,QAAM,WAAW,oBAAI,IAAY,CAAC,MAAM,CAAC;AAEzC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,QAAQ,MAAM,MAAM;AAC1B,QAAI,MAAM,WAAW,KAAK,MAAM,UAAU,SAAS,GAAG;AACpD,YAAM,WAAW,MAAM,UAAU,MAAM,UAAU,SAAS,CAAC;AAC3D,WAAK,IAAI,MAAM,QAAQ;AAAA,QACrB,QAAQ,MAAM;AAAA,QACd,UAAU,MAAM;AAAA,QAChB,gBAAgB,SAAS;AAAA,QACzB,MAAM,MAAM;AAAA,QACZ,YAAY,kBAAkB,MAAM,SAAS;AAAA,MAC/C,CAAC;AAAA,IACH;AACA,QAAI,MAAM,YAAY,SAAU;AAEhC,UAAM,WAAW,iBAAiB,OAAO,MAAM,aAAa,MAAM,MAAM,CAAC;AACzE,eAAW,CAAC,OAAO,IAAI,KAAK,UAAU;AACpC,UAAI,SAAS,IAAI,KAAK,EAAG;AACzB,eAAS,IAAI,KAAK;AAClB,YAAM,KAAK;AAAA,QACT,QAAQ;AAAA,QACR,UAAU,MAAM,WAAW;AAAA,QAC3B,MAAM,CAAC,GAAG,MAAM,MAAM,KAAK;AAAA,QAC3B,WAAW,CAAC,GAAG,MAAM,WAAW,IAAI;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE;AAAA,IACvC,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,cAAc,EAAE,MAAM;AAAA,EACtE;AACA,SAAO,wBAAwB,MAAM;AAAA,IACnC,QAAQ;AAAA,IACR;AAAA,IACA,eAAe,cAAc;AAAA,EAC/B,CAAC;AACH;AAKO,IAAM,wCAAwC;AAC9C,IAAM,oCAAoC;AAU1C,SAAS,0BACd,OACA,QACA,QAAgB,uCACc;AAC9B,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,mCAAmC,MAAM;AAAA,MAC9C,QAAQ;AAAA,MACR;AAAA,MACA,cAAc,CAAC;AAAA,MACf,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAQA,QAAM,OAAO,oBAAI,IAAkC;AACnD,QAAM,QAAiB,CAAC,EAAE,QAAQ,UAAU,GAAG,MAAM,KAAK,CAAC;AAC3D,QAAM,WAAW,oBAAI,IAAY,CAAC,MAAM,CAAC;AAEzC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,QAAQ,MAAM,MAAM;AAC1B,QAAI,MAAM,WAAW,KAAK,MAAM,MAAM;AACpC,WAAK,IAAI,MAAM,QAAQ;AAAA,QACrB,QAAQ,MAAM;AAAA,QACd,UAAU,MAAM;AAAA,QAChB,UAAU,MAAM,KAAK;AAAA,QACrB,YAAY,MAAM,KAAK;AAAA,MACzB,CAAC;AAAA,IACH;AACA,QAAI,MAAM,YAAY,MAAO;AAE7B,UAAM,WAAW,iBAAiB,OAAO,MAAM,cAAc,MAAM,MAAM,CAAC;AAC1E,eAAW,CAAC,OAAO,IAAI,KAAK,UAAU;AACpC,UAAI,SAAS,IAAI,KAAK,EAAG;AACzB,eAAS,IAAI,KAAK;AAClB,YAAM,KAAK,EAAE,QAAQ,OAAO,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE;AAAA,IACtC,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,cAAc,EAAE,MAAM;AAAA,EACtE;AACA,SAAO,mCAAmC,MAAM;AAAA,IAC9C,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,EACtB,CAAC;AACH;AAqBO,SAAS,wBACd,OACA,QAC4B;AAC5B,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,iCAAiC,MAAM;AAAA,MAC5C,QAAQ;AAAA,MACR,cAAc,CAAC;AAAA,MACf,UAAU;AAAA,MACV,sBAAsB;AAAA,MACtB,sBAAsB;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAI5C,QAAM,QAAkB,CAAC,MAAM;AAC/B,MAAI,MAAM,SAAS,SAAS,aAAa;AACvC,eAAW,UAAU,MAAM,cAAc,MAAM,GAAG;AAChD,YAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,UAAI,EAAE,SAAS,SAAS,SAAU;AAClC,YAAM,QAAQ,MAAM,kBAAkB,EAAE,MAAM;AAC9C,UAAI,MAAM,SAAS,SAAS,SAAU,OAAM,KAAK,EAAE,MAAM;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,eAA4B,CAAC;AACnC,QAAM,WAAW,oBAAI,IAAY;AACjC,MAAI,uBAAuB;AAC3B,aAAW,OAAO,OAAO;AACvB,eAAW,UAAU,MAAM,cAAc,GAAG,GAAG;AAC7C,YAAM,IAAI,MAAM,kBAAkB,MAAM;AAExC,UAAI,EAAE,SAAS,SAAS,SAAU;AAClC,UAAI,EAAE,eAAe,WAAW,UAAU;AACxC,YAAI,CAAC,SAAS,IAAI,EAAE,EAAE,GAAG;AACvB,mBAAS,IAAI,EAAE,EAAE;AACjB,uBAAa,KAAK,CAAC;AAAA,QACrB;AAAA,MACF,WAAW,EAAE,eAAe,WAAW,WAAW;AAChD,+BAAuB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAKA,MAAI,uBAAuB;AAC3B,aAAW,OAAO,OAAO;AACvB,eAAW,UAAU,MAAM,aAAa,GAAG,GAAG;AAC5C,YAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,UAAI,EAAE,SAAS,SAAS,SAAU;AAClC,UAAI,EAAE,eAAe,WAAW,SAAU,yBAAwB;AAAA,IACpE;AAAA,EACF;AAEA,eAAa;AAAA,IACX,CAAC,GAAG,MACF,EAAE,OAAO,cAAc,EAAE,MAAM,KAC/B,EAAE,OAAO,cAAc,EAAE,MAAM,KAC/B,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,EAC3B;AAEA,SAAO,iCAAiC,MAAM;AAAA,IAC5C,QAAQ;AAAA,IACR;AAAA,IACA,UAAU,aAAa,SAAS,KAAK,uBAAuB;AAAA,IAC5D;AAAA,IACA;AAAA,EACF,CAAC;AACH;;;AC55BA,SAAS,YAAYG,KAAI,YAAY,oBAAoB;AACzD,OAAOC,WAAU;AACjB,YAAY,iBAAiB;;;ACF7B,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AAajB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA;AAAA,OACK;;;ACPP,SAAS,oBAAoB;AA2EtB,IAAM,oBAAoB;AAEjC,IAAM,eAAN,cAA2B,aAAa;AAAC;AAIlC,IAAM,WAAyB,IAAI,aAAa;AAIvD,SAAS,gBAAgB,CAAC;AAEnB,SAAS,cAAuC,UAAsC;AAC3F,WAAS,KAAK,mBAAmB,QAAQ;AAC3C;AAkBO,SAAS,sBAAsB,OAAkB,MAAiC;AACvF,QAAM,EAAE,QAAQ,IAAI;AAEpB,QAAM,cAAc,CAAC,YAA0D;AAC7E,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,SAAS,EAAE,MAAM,QAAQ,WAAW;AAAA,IACtC,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,CAAC,YAAmC;AACxD,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,SAAS,EAAE,IAAI,QAAQ,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AACA,QAAM,cAAc,CAAC,YAA0D;AAC7E,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,SAAS,EAAE,MAAM,QAAQ,WAAW;AAAA,IACtC,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,CAAC,YAAmC;AACxD,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,SAAS,EAAE,IAAI,QAAQ,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AACA,QAAM,qBAAqB,CAAC,YAA0D;AACpF,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,SAAS,EAAE,IAAI,QAAQ,KAAK,SAAS,QAAQ,WAAW;AAAA,IAC1D,CAAC;AAAA,EACH;AAEA,QAAM,GAAG,aAAa,WAAW;AACjC,QAAM,GAAG,eAAe,aAAa;AACrC,QAAM,GAAG,aAAa,WAAW;AACjC,QAAM,GAAG,eAAe,aAAa;AACrC,QAAM,GAAG,yBAAyB,kBAAkB;AAEpD,SAAO,MAAM;AACX,UAAM,IAAI,aAAa,WAAW;AAClC,UAAM,IAAI,eAAe,aAAa;AACtC,UAAM,IAAI,aAAa,WAAW;AAClC,UAAM,IAAI,eAAe,aAAa;AACtC,UAAM,IAAI,yBAAyB,kBAAkB;AAAA,EACvD;AACF;;;AD9GA,IAAM,6BAAmE;AAAA,EACvE,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,UAAU;AACZ;AAEO,SAAS,mBAAmB,QAA8B;AAC/D,SAAO,OAAO,eAAe,2BAA2B,OAAO,QAAQ;AACzE;AAEA,SAAS,cACP,QACA,MACA,eACA,SACA,SACA,KACiB;AACjB,SAAO;AAAA,IACL,IAAI,GAAG,OAAO,EAAE,IAAI,aAAa;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,IACjB,aAAa,mBAAmB,MAAM;AAAA,IACtC,UAAU,KAAK;AAAA,IACf;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,IAAI,IAAI,CAAC,EAAE,YAAY;AAAA,EAC9C;AACF;AAMA,IAAM,qBAAiF,CAAC;AAAA,EACtF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,aAAgC,CAAC;AACvC,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,KAAK,aAAc;AAClC,QAAI,YAAY;AAChB,eAAW,UAAU,MAAM,cAAc,EAAE,GAAG;AAC5C,YAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,UAAI,EAAE,SAAS,KAAK,SAAU;AAC9B,YAAM,SAAS,MAAM,kBAAkB,EAAE,MAAM;AAG/C,UAAI,OAAO,SAASC,UAAS,aAAc;AAC3C,UAAI,OAAO,SAAS,KAAK,YAAY;AACnC,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,WAAW;AACd,iBAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG,KAAK,YAAY,IAAI,EAAE,WAAW,KAAK,QAAQ,cAAc,KAAK,UAAU;AAAA,UAC/E,EAAE,QAAQ,GAAG;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,IAAM,oBAA+E,CAAC;AAAA,EACpF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,aAAgC,CAAC;AACvC,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,KAAK,SAAU;AAC9B,UAAM,QAAQ,EAAE,KAAK,KAAK;AAC1B,QAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,iBAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG,KAAK,QAAQ,IAAI,EAAE,+BAA+B,KAAK,KAAK;AAAA,UAC/D,EAAE,QAAQ,GAAG;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,IAAM,qBAAiF,CAAC;AAAA,EACtF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,QAAQ,IAAI,oBAAI,IAAI,CAAC,KAAK,QAAQ,CAAC;AAChG,QAAM,aAAgC,CAAC;AACvC,QAAM,YAAY,CAAC,QAAQ,UAAU;AACnC,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,KAAK,SAAU;AAC9B,QAAI,KAAK,gBAAgB,EAAE,WAAW,KAAK,aAAc;AACzD,QAAI,CAAC,SAAS,IAAI,EAAE,UAAU,GAAG;AAC/B,YAAM,eAAe,CAAC,GAAG,QAAQ,EAAE,KAAK,KAAK;AAC7C,iBAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG,KAAK,QAAQ,SAAS,MAAM,mBAAmB,EAAE,UAAU,cAAc,YAAY;AAAA,UACxF,EAAE,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,IAAM,sBAAoF,CAAC;AAAA,EACzF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,aAAgC,CAAC;AACvC,QAAM,QAAQ,KAAK;AACnB,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,KAAK,SAAU;AAC9B,UAAM,SAAS,UAAU,SAAY,eAAe,OAAO,IAAI,KAAK,IAAI,eAAe,OAAO,EAAE;AAChG,QAAI,OAAO,gBAAgB,KAAK,aAAa;AAC3C,iBAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG,KAAK,QAAQ,IAAI,EAAE,qBAAqB,OAAO,aAAa,MAAM,KAAK,WAAW;AAAA,UACrF,EAAE,QAAQ,IAAI,MAAM,CAAC,EAAE,EAAE;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,IAAM,wBAAuF,CAAC;AAAA,EAC5F;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,aAAgC,CAAC;AAKvC,QAAM,YAAY,CAAC,SACjB,KAAK,SAAS,UAAa,KAAK,SAAS;AAE3C,QAAM,YAAY,CAAC,OAAO,UAAU;AAClC,UAAM,IAAI;AACV,QAAI,EAAE,SAASA,UAAS,YAAa;AACrC,UAAM,MAAM;AACZ,UAAM,OAAO,IAAI,gBAAgB,CAAC;AAElC,QAAI,UAAU,eAAe,GAAG;AAI9B,iBAAW,UAAU,MAAM,cAAc,KAAK,GAAG;AAC/C,cAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,YAAI,EAAE,SAASC,UAAS,YAAa;AACrC,cAAM,UAAU,MAAM,kBAAkB,EAAE,MAAM;AAGhD,YAAI,QAAQ,SAASD,UAAS,aAAc;AAC5C,YAAI,QAAQ,SAASA,UAAS,aAAc;AAC5C,cAAM,KAAK;AACX,mBAAW,QAAQ,YAAY,GAAG;AAChC,cAAI,KAAK,WAAW,GAAG,OAAQ;AAC/B,gBAAM,WAAW,KAAK,KAAK,MAAM;AACjC,cAAI,CAAC,SAAU;AACf,gBAAM,SAAS,mBAAmB,KAAK,QAAQ,UAAU,GAAG,QAAQ,GAAG,aAAa;AACpF,cAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,uBAAW;AAAA,cACT;AAAA,gBACE;AAAA,gBACA;AAAA,gBACA,GAAG,KAAK,kBAAkB,KAAK,MAAM,IAAI,QAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,aAAa;AAAA,gBAClF,OAAO;AAAA,gBACP,EAAE,QAAQ,OAAO,OAAO;AAAA,gBACxB;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,aAAa,GAAG;AAC5B,YAAM,mBAAmB,IAAI;AAC7B,iBAAW,cAAc,sBAAsB,GAAG;AAChD,cAAM,WAAW,KAAK,WAAW,OAAO;AACxC,YAAI,CAAC,SAAU;AACf,cAAM,SAAS,0BAA0B,YAAY,UAAU,gBAAgB;AAC/E,YAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,qBAAW;AAAA,YACT;AAAA,cACE;AAAA,cACA;AAAA,cACA,GAAG,KAAK,gBAAgB,WAAW,OAAO,IAAI,QAAQ;AAAA,cACtD,OAAO;AAAA,cACP,EAAE,QAAQ,MAAM;AAAA,cAChB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,kBAAkB,GAAG;AACjC,iBAAW,YAAY,iBAAiB,GAAG;AACzC,cAAM,WAAW,KAAK,SAAS,OAAO;AACtC,YAAI,CAAC,SAAU;AACf,cAAM,mBAAmB,KAAK,SAAS,SAAS,IAAI;AACpD,cAAM,SAAS,qBAAqB,UAAU,UAAU,gBAAgB;AACxE,YAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,qBAAW;AAAA,YACT;AAAA,cACE;AAAA,cACA;AAAA,cACA,GAAG,KAAK,qBAAqB,SAAS,OAAO,IAAI,QAAQ;AAAA,cACzD,OAAO;AAAA,cACP,EAAE,QAAQ,MAAM;AAAA,cAChB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,gBAAgB,GAAG;AAC/B,iBAAW,OAAO,eAAe,GAAG;AAClC,cAAM,WAAW,KAAK,IAAI,OAAO;AACjC,YAAI,CAAC,SAAU;AACf,cAAM,SAAS,mBAAmB,KAAK,QAAQ;AAC/C,YAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,qBAAW;AAAA,YACT;AAAA,cACE;AAAA,cACA;AAAA,cACA,GAAG,KAAK,mBAAmB,IAAI,OAAO,IAAI,QAAQ;AAAA,cAClD,OAAO;AAAA,cACP,EAAE,QAAQ,MAAM;AAAA,cAChB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAEA,IAAM,mBAAmG;AAAA,EACvG,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe;AACjB;AAgBO,SAAS,mBACd,OACAE,aACA,UACA,KACqD;AACrD,MAAI,SAAS,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,YAAY,CAAC,EAAE;AAClE,QAAM,MAAM,oBAAoB,OAAO,UAAU,GAAG;AACpD,QAAM,WAAW,IAAI,OAAO,CAAC,MAAM;AACjC,QAAI,EAAE,gBAAgB,QAAS,QAAO;AACtC,WACE,EAAE,QAAQ,WAAWA,eACrB,EAAE,QAAQ,MAAM,SAASA,WAAU,MAAM;AAAA,EAE7C,CAAC;AACD,SAAO,EAAE,SAAS,SAAS,WAAW,GAAG,YAAY,SAAS;AAChE;AAEO,SAAS,oBACd,OACA,UACA,KACmB;AACnB,QAAM,MAAyB,CAAC;AAChC,aAAW,UAAU,UAAU;AAC7B,UAAM,YAAY,iBAAiB,OAAO,KAAK,IAAI;AACnD,UAAM,aAAa,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,MAAM,IAAI,CAAC;AACtE,eAAW,KAAK,WAAY,KAAI,KAAK,CAAC;AAAA,EACxC;AACA,SAAO;AACT;AA4BO,SAAS,yBACd,OACA,UACA,QACoB;AAKpB,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,QAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,QAAM,MAA0B,CAAC;AACjC,aAAW,UAAU,UAAU;AAC7B,UAAM,IAAI,kBAAkB,OAAO,QAAQ,QAAQ,IAAI;AACvD,QAAI,CAAC,EAAG;AACR,QAAI,KAAK;AAAA,MACP,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,GAAI,OAAO,gBAAgB,SAAY,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;AAAA,MAC9E,UAAU,OAAO;AAAA,MACjB,aAAa,mBAAmB,MAAM;AAAA,MACtC,UAAU,OAAO,KAAK;AAAA,MACtB,OAAO,EAAE;AAAA,MACT,QAAQ,EAAE;AAAA,IACZ,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAOA,SAAS,uBAAuB,UAA8C;AAG5E,MAAI,MAAM,QAAQ,QAAQ,EAAG,QAAO,SAAS,KAAK,KAAK;AACvD,SAAO,OAAO,QAAQ;AACxB;AAEA,SAAS,oBACP,OACA,QACA,UACA,kBACS;AACT,QAAM,WAAW,CAAC,GAAG,MAAM,cAAc,MAAM,GAAG,GAAG,MAAM,aAAa,MAAM,CAAC;AAC/E,aAAW,UAAU,UAAU;AAC7B,UAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,QAAI,EAAE,SAAS,SAAU;AAGzB,QAAI,qBAAqB,OAAW,QAAO;AAC3C,QAAI,EAAE,WAAW,oBAAoB,EAAE,WAAW,iBAAkB,QAAO;AAAA,EAC7E;AACA,SAAO;AACT;AAMA,SAAS,2BACP,OACA,MACA,QACe;AACf,MAAI,QAAuB;AAC3B,QAAM,YAAY,CAAC,QAAQ,UAAU;AACnC,QAAI,UAAU,KAAM;AACpB,QAAI,WAAW,OAAQ;AACvB,QAAK,MAAoB,SAAS,KAAK,SAAU;AACjD,UAAM,SACJ,KAAK,UAAU,SACX,eAAe,OAAO,QAAQ,KAAK,KAAK,IACxC,eAAe,OAAO,MAAM;AAClC,QAAI,OAAO,cAAc,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,EAAG,SAAQ;AAAA,EACrE,CAAC;AACD,SAAO;AACT;AAEA,SAAS,kBACP,OACA,QACA,QACA,MACoB;AACpB,QAAM,OAAO,OAAO;AACpB,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK,cAAc;AACjB,UAAI,KAAK,SAAS,KAAK,cAAc;AACnC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,SAAS,KAAK,YAAY,gBAAgB,KAAK,QAAQ,cAAc,KAAK,UAAU;AAAA,QAC9F;AAAA,MACF;AAGA,UAAI,KAAK,SAAS,KAAK,YAAY;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,GAAG,KAAK,YAAY,uBAAuB,KAAK,UAAU,wBAAwB,KAAK,QAAQ;AAAA,QACzG;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,aAAa;AAChB,UAAI,KAAK,SAAS,KAAK,UAAU;AAC/B,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,SAAS,KAAK,QAAQ,8BAA8B,KAAK,KAAK;AAAA,QACxE;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,gBAAgB;AACnB,YAAM,aAAa,KAAK,UAAU,SAAY,aAAa,KAAK,KAAK,KAAK;AAC1E,UAAI,KAAK,SAAS,KAAK,UAAU;AAC/B,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,MAAM,KAAK,QAAQ,iCAAiC,KAAK,WAAW,GAAG,UAAU;AAAA,QAC3F;AAAA,MACF;AAUA,YAAM,UAAU,2BAA2B,OAAO,MAAM,MAAM;AAC9D,UAAI,SAAS;AACX,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,uCAAuC,OAAO,OAAO,KAAK,QAAQ,8BAA8B,KAAK,WAAW,GAAG,UAAU,8BAAyB,KAAK,QAAQ;AAAA,QAC7K;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,iBAAiB;AACpB,YAAM,YAAY,KAAK,QAAQ;AAG/B,UAAI,KAAK,SAASF,UAAS,aAAa;AACtC,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,0DAA0D,SAAS;AAAA,QAC7E;AAAA,MACF;AAGA,YAAM,sBAAsB,KAAK,SAAS,UAAa,KAAK,SAAS;AACrE,UACE,uBACA,KAAK,SAASA,UAAS,gBACvB,oBAAoB,OAAO,QAAQC,UAAS,WAAW,GACvD;AACA,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,eAAe,uBAAuB,KAAK,QAAQ;AAGzD,UAAI,KAAK,iBAAiB,UAAa,WAAW,KAAK,cAAc;AACnE,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,SAAS,KAAK,QAAQ,cAAc,KAAK,YAAY,eAAe,YAAY;AAAA,QAC1F;AAAA,MACF;AAEA,UAAI,oBAAoB,OAAO,QAAQ,KAAK,UAAU,KAAK,YAAY,GAAG;AACxE,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QACE,KAAK,iBAAiB,SAClB,uBAAuB,KAAK,QAAQ,YAAY,KAAK,YAAY,sBAAsB,YAAY,gBACnG,uBAAuB,KAAK,QAAQ,2BAA2B,YAAY;AAAA,QACnF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAUA,eAAsB,eAAe,YAAuC;AAC1E,MAAI;AACJ,MAAI;AACF,UAAM,MAAME,IAAG,SAAS,YAAY,MAAM;AAAA,EAC5C,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;AAAA,EACR;AACA,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QAAM,OAAmB,iBAAiB,MAAM,IAAI;AACpD,SAAO,KAAK;AACd;AASO,IAAM,sBAAN,MAA0B;AAAA,EACd;AAAA,EACA;AAAA,EACT,OAA2B;AAAA,EAEnC,YAAY,SAAiB,UAAkB,iBAAiB;AAC9D,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,OAAO,GAAsC;AACjD,QAAI,CAAC,KAAK,KAAM,OAAM,KAAK,QAAQ;AACnC,QAAI,KAAK,KAAM,IAAI,EAAE,EAAE,EAAG,QAAO;AACjC,SAAK,KAAM,IAAI,EAAE,EAAE;AACnB,UAAMA,IAAG,MAAMC,MAAK,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,UAAMD,IAAG,WAAW,KAAK,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,MAAM;AAI/D,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,KAAK;AAAA,MACd,SAAS,EAAE,WAAW,EAAE;AAAA,IAC1B,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAsC;AAC1C,QAAI;AACF,YAAM,MAAM,MAAMA,IAAG,SAAS,KAAK,MAAM,MAAM;AAC/C,aAAO,IACJ,MAAM,IAAI,EACV,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAoB;AAAA,IACtD,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,UAAyB;AACrC,SAAK,OAAO,oBAAI,IAAI;AACpB,UAAM,WAAW,MAAM,KAAK,QAAQ;AACpC,eAAW,KAAK,SAAU,MAAK,KAAK,IAAI,EAAE,EAAE;AAAA,EAC9C;AACF;;;ADvpBA;AAAA,EACE,YAAAE;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAuDP,IAAM,UAAU,KAAK,KAAK;AAC1B,IAAM,SAAS,KAAK;AAMpB,IAAM,2BAAmD;AAAA,EACvD,OAAO;AAAA,EACP,aAAa,IAAI;AAAA,EACjB,cAAc,IAAI;AAAA,EAClB,eAAe,IAAI;AAAA,EACnB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,SAAS;AACX;AAGA,IAAM,8BAA8B;AAEpC,SAAS,6BAAqD;AAC5D,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,YAAY,KAAK,MAAM,GAAG;AAChC,UAAM,SAAS,EAAE,GAAG,yBAAyB;AAC7C,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC9C,UAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO,CAAC,IAAI;AAAA,IACzE;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ;AAAA,MACN,qDAAsD,IAAc,OAAO;AAAA,IAC7E;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,qBACd,UACA,WACQ;AACR,QAAM,MAAM,aAAa,2BAA2B;AACpD,SAAO,IAAI,QAAQ,KAAK;AAC1B;AAaA,IAAM,8BAA8B;AAAA,EAClC,WAAW;AAAA,EACX,UAAU;AACZ;AAEA,SAAS,gCAAyE;AAChF,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,YAAY,KAAK,MAAM,GAAG;AAChC,UAAM,SAAS,EAAE,GAAG,4BAA4B;AAChD,QACE,OAAO,UAAU,cAAc,YAC/B,OAAO,SAAS,UAAU,SAAS,KACnC,UAAU,aAAa,GACvB;AACA,aAAO,YAAY,KAAK,MAAM,UAAU,SAAS;AAAA,IACnD;AACA,QACE,OAAO,UAAU,aAAa,YAC9B,OAAO,SAAS,UAAU,QAAQ,KAClC,UAAU,YAAY,GACtB;AACA,aAAO,WAAW,UAAU;AAAA,IAC9B;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ;AAAA,MACN,wDAAyD,IAAc,OAAO;AAAA,IAChF;AACA,WAAO;AAAA,EACT;AACF;AAYA,SAAS,4BAA4B,OAAoC;AACvE,aAAW,OAAO,CAAC,6BAA6B,kBAAkB,GAAG;AACnE,UAAM,IAAI,MAAM,GAAG;AACnB,QAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,EAAG,QAAO;AACxD,QAAI,OAAO,MAAM,UAAU;AACzB,YAAM,IAAI,OAAO,CAAC;AAClB,UAAI,OAAO,SAAS,CAAC,EAAG,QAAO;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAsC;AAChE,SAAO,4BAA4B,KAAK,UAAU;AACpD;AAWA,SAAS,4BAA4B,OAAoC;AACvE,QAAM,SAAS,4BAA4B,KAAK;AAChD,QAAM,QAAQ,aAAa,OAAO,cAAc,eAAe,UAAU;AACzE,QAAM,SAAS,aAAa,OAAO,uBAAuB,aAAa;AACvE,QAAM,QAAQ,QAAQ,GAAG,SAAS,GAAG,MAAM,MAAM,EAAE,GAAG,KAAK,KAAK;AAChE,MAAI,WAAW,UAAa,MAAO,QAAO,GAAG,MAAM,OAAO,KAAK;AAC/D,MAAI,WAAW,OAAW,QAAO,QAAQ,MAAM;AAC/C,MAAI,MAAO,QAAO,YAAY,KAAK;AACnC,SAAO;AACT;AAKA,IAAM,oBAA4C;AAAA,EAChD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,SAAS,wBAAwB,OAAoC;AACnE,QAAM,IAAI,MAAM,sBAAsB;AACtC,MAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,EAAG,QAAO;AACxD,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,OAAO,SAAS,CAAC,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;AAQA,SAAS,+BAA+B,OAAoC;AAC1E,QAAM,OAAO,wBAAwB,KAAK;AAC1C,MAAI,SAAS,UAAa,SAAS,GAAG;AACpC,UAAM,OAAO,kBAAkB,IAAI,KAAK,UAAU,IAAI;AACtD,UAAM,SAAS,aAAa,OAAO,yBAAyB;AAC5D,WAAO,SAAS,QAAQ,IAAI,KAAK,MAAM,KAAK,QAAQ,IAAI;AAAA,EAC1D;AAKA,QAAM,UAAU,aAAa,OAAO,YAAY;AAChD,MAAI,WAAW,YAAY,YAAY,CAAC,QAAQ,KAAK,OAAO,GAAG;AAC7D,UAAM,OAAO,aAAa,OAAO,kBAAkB,iBAAiB,eAAe;AACnF,WAAO,OAAO,GAAG,OAAO,kBAAkB,IAAI,KAAK;AAAA,EACrD;AACA,SAAO;AACT;AAOA,SAAS,gBAAgB,MAA0B;AACjD,SACE,KAAK,WAAW,WAChB,4BAA4B,KAAK,UAAU,KAC3C,+BAA+B,KAAK,UAAU,KAC9C;AAEJ;AAeA,SAAS,OAAO,KAA4B;AAC1C,SAAO,IAAI,KAAK,IAAI,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,YAAY;AAChE;AASA,IAAM,6BAA6B,oBAAI,IAAY;AACnD,SAAS,qBAAqB,SAAuB;AACnD,MAAI,2BAA2B,IAAI,OAAO,EAAG;AAC7C,6BAA2B,IAAI,OAAO;AACtC,UAAQ;AAAA,IACN,yEAAyE,OAAO;AAAA,EAClF;AACF;AAgBA,IAAM,4BAA4B,oBAAI,IAAY;AAClD,SAAS,iBAAiB,aAA2B;AACnD,MAAI,0BAA0B,IAAI,WAAW,EAAG;AAChD,4BAA0B,IAAI,WAAW;AACzC,UAAQ;AAAA,IACN,UAAU,WAAW;AAAA,EACvB;AACF;AAQA,SAAS,aAAa,UAAmB,MAAoC;AAC3E,aAAW,KAAK,MAAM;AACpB,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO;AAAA,EACpD;AACA,SAAO;AACT;AAEA,SAAS,SAAS,SAAqB,MAAoC;AACzE,SAAO,aAAa,KAAK,YAAY,GAAG,IAAI;AAC9C;AAEA,SAAS,YAAY,GAA2C;AAC9D,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACF,WAAO,IAAI,IAAI,CAAC,EAAE;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,YAAY,MAAsC;AACzD,SACE,SAAS,MAAM,kBAAkB,iBAAiB,eAAe,KACjE,YAAY,SAAS,MAAM,YAAY,UAAU,CAAC;AAEtD;AASA,SAAS,eAAe,MAAuB;AAC7C,QAAM,IAAI,KAAK,YAAY;AAC3B,SACE,MAAM,eACN,MAAM,mBACN,MAAM,SACN,MAAM,WACN,wBAAwB,KAAK,CAAC;AAElC;AAYA,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAE3B,SAAS,QAAQ,GAAmB;AAClC,SAAO,EAAE,MAAM,IAAI,EAAE,KAAK,GAAG;AAC/B;AAEA,SAAS,eAAe,SAAqC;AAC3D,QAAM,MAAM,QAAQ,YAAY,GAAG;AACnC,MAAI,QAAQ,GAAI,QAAO;AACvB,UAAQ,QAAQ,MAAM,GAAG,EAAE,YAAY,GAAG;AAAA,IACxC,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AASA,SAAS,sBACP,UACA,aACA,UACe;AACf,MAAI,IAAI,QAAQ,QAAQ,EAAE,QAAQ,cAAc,EAAE;AAMlD,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,UAAM,UAAU,QAAQC,MAAK,QAAQ,UAAU,aAAa,YAAY,EAAE,CAAC;AAC3E,UAAM,SAAS,QAAQ,SAAS,GAAG,IAAI,UAAU,GAAG,OAAO;AAC3D,QAAI,EAAE,WAAW,MAAM,EAAG,QAAO,EAAE,MAAM,OAAO,MAAM;AAAA,EACxD;AACA,QAAM,OAAO,aAAa;AAC1B,MAAI,QAAQ,SAAS,OAAO,KAAK,SAAS,GAAG;AAC3C,UAAM,YAAY,QAAQ,IAAI;AAC9B,UAAM,SAAS,IAAI,SAAS;AAC5B,UAAM,MAAM,EAAE,YAAY,MAAM;AAChC,QAAI,QAAQ,GAAI,QAAO,EAAE,MAAM,MAAM,OAAO,MAAM;AAClD,UAAM,OAAO,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI;AACtD,QAAI,MAAM;AACR,YAAM,aAAa,IAAI,IAAI;AAC3B,YAAM,OAAO,EAAE,YAAY,UAAU;AACrC,UAAI,SAAS,GAAI,QAAO,EAAE,MAAM,OAAO,WAAW,MAAM;AAAA,IAC1D;AAAA,EACF;AACA,MAAI,EAAE,QAAQ,cAAc,EAAE,EAAE,QAAQ,QAAQ,EAAE;AAClD,SAAO,EAAE,SAAS,IAAI,IAAI;AAC5B;AA0BA,IAAM,iBAAiB,oBAAI,IAGzB;AAOF,SAAS,iBAAiB,aAAqB,MAAmC;AAChF,MAAI,CAAC,YAAY,SAAS,KAAK,EAAG,QAAO;AACzC,MAAI,QAAQ,eAAe,IAAI,WAAW;AAC1C,MAAI,UAAU,QAAW;AACvB,YAAQ;AACR,UAAM,UAAU,GAAG,WAAW;AAC9B,QAAI;AACF,UAAI,WAAW,OAAO,GAAG;AACvB,cAAM,MAAM,KAAK,MAAM,aAAa,SAAS,MAAM,CAAC;AACpD,cAAM,WAAW,IAAgB,8BAAkB,GAAY;AAC/D,gBAAQ,EAAE,UAAU,KAAKA,MAAK,QAAQ,OAAO,EAAE;AAAA,MACjD;AAAA,IACF,QAAQ;AACN,cAAQ;AAAA,IACV;AACA,mBAAe,IAAI,aAAa,KAAK;AAAA,EACvC;AACA,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,oBAAoB;AAAA,MAC7C,MAAM,SAAS,UAAa,OAAO,SAAS,IAAI,IAAI,OAAO;AAAA,MAC3D,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,CAAC,OAAO,CAAC,IAAI,OAAQ,QAAO;AAChC,UAAM,OAAO,MAAM,SAAS,cAAc;AAC1C,UAAM,WAAWA,MAAK,QAAQ,MAAM,KAAK,MAAM,IAAI,MAAM;AACzD,WAAO,EAAE,UAAU,UAAU,GAAI,IAAI,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC,EAAG;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,iBACP,MACA,aACA,UACiB;AACjB,QAAM,WAAW,KAAK,WAAW,kBAAkB;AACnD,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,QAAM,YAAY,KAAK,WAAW,gBAAgB;AAClD,MAAI,OACF,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,IAAI,YAAY;AAG5E,QAAM,MAAM,QAAQ,QAAQ,EAAE,QAAQ,cAAc,EAAE;AACtD,QAAM,WAAW,iBAAiB,KAAK,IAAI;AAC3C,MAAI,gBAAgB;AACpB,MAAI;AACJ,MAAI,UAAU;AACZ,sBAAkB,sBAAsB,UAAU,aAAa,QAAQ,KAAK;AAC5E,oBAAgB,SAAS;AACzB,QAAI,SAAS,SAAS,OAAW,QAAO,SAAS;AAAA,EACnD;AACA,QAAM,UAAU,sBAAsB,eAAe,aAAa,QAAQ;AAC1E,MAAI,CAAC,QAAS,QAAO;AAKrB,MAAI,CAAC,YAAY,IAAI,SAAS,KAAK,KAAK,QAAQ,WAAW,OAAO,KAAK,aAAa,MAAM;AACxF,qBAAiB,YAAY,IAAI;AAAA,EACnC;AACA,QAAM,QAAQ,KAAK,WAAW,kBAAkB;AAChD,QAAM,KAAK,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACnE,SAAO;AAAA,IACL;AAAA,IACA,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IACrC,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,IACnB,GAAI,mBAAmB,oBAAoB,UAAU,EAAE,gBAAgB,IAAI,CAAC;AAAA,EAC9E;AACF;AAyBO,SAAS,yBACd,OACA,aACA,SACQ;AAGR,MAAI,MAAM,QAAQ,OAAO,aAAa,OAAO,CAAC,EAAG,QAAO;AACxD,MAAI,OAAsB;AAC1B,QAAM,YAAY,CAAC,KAAK,UAAU;AAChC,UAAM,IAAI;AACV,QAAI,EAAE,SAASC,UAAS,YAAY,EAAE,YAAY,YAAa;AAG/D,QAAI,EAAE,kBAAkB,OAAQ;AAChC,UAAM,IAAI,EAAE;AACZ,QAAI,CAAC,EAAG;AACR,SAAK,YAAY,KAAK,QAAQ,SAAS,IAAI,CAAC,EAAE,OAAO,CAAC,QAAQ,EAAE,SAAS,KAAK,SAAS;AACrF,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACD,SAAO,QAAQ;AACjB;AASO,SAAS,uBACd,OACA,aACA,eACA,UACQ;AACR,QAAM,UAAU,yBAAyB,OAAO,aAAa,SAAS,OAAO;AAC7E,QAAM,aAAa,OAAO,aAAa,OAAO;AAC9C,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,WAAW,eAAe,OAAO;AACvC,UAAM,OAAiB;AAAA,MACrB,IAAI;AAAA,MACJ,MAAMA,UAAS;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/B,GAAI,SAAS,kBAAkB,EAAE,cAAc,SAAS,gBAAgB,IAAI,CAAC;AAAA,MAC7E,eAAe;AAAA,IACjB;AACA,UAAM,QAAQ,YAAY,IAAI;AAAA,EAChC;AACA,QAAM,aAAa,mBAAmBC,UAAS,UAAU,eAAe,UAAU;AAClF,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,OAAkB;AAAA,MACtB,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,MAAMA,UAAS;AAAA,MACf,YAAYC,YAAW;AAAA,IACzB;AACA,UAAM,eAAe,YAAY,eAAe,YAAY,IAAI;AAAA,EAClE;AACA,SAAO;AACT;AAKA,SAAS,mBAAmB,MAAqB,QAAgB,QAAwB;AACvF,SAAO,eAAe,QAAQ,QAAQ,IAAI;AAC5C;AAEA,SAAS,mBAAmB,MAAqB,QAAgB,QAAwB;AACvF,SAAO,eAAe,QAAQ,QAAQ,IAAI;AAC5C;AAEA,IAAM,sBAAsB;AAC5B,IAAM,mBAAmB;AAWzB,IAAM,oBAAoB,oBAAI,IAAmB;AAAA,EAC/CD,UAAS;AAAA,EACTA,UAAS;AAAA,EACTA,UAAS;AACX,CAAC;AAUD,IAAM,wBAAwB;AAC9B,IAAM,0BAA0B;AAChC,IAAM,0BAA0B;AAqBhC,SAAS,sBAAsB,MAAmC;AAChE,MAAI,SAAS,UAAa,SAAS,EAAG,QAAO;AAC7C,SAAO,SAAS,yBAAyB,SAAS;AACpD;AAQA,SAAS,uBAAuB,MAAmC;AACjE,SAAO,SAAS,2BAA2B,SAAS;AACtD;AASA,SAAS,2BAA2B,MAAmC;AACrE,SACE,SAAS,yBACT,SAAS,2BACT,SAAS;AAEb;AASA,SAAS,2BACP,OACA,aACA,eACA,eACQ;AACR,QAAM,KAAK,mBAAmB,aAAa,eAAe,aAAa;AACvE,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAA6B;AAAA,IACjC;AAAA,IACA,MAAMD,UAAS;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,cAAc,YAAY;AAAA,IACzC;AAAA,IACA,eAAe;AAAA,EACjB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAUA,SAAS,qBAAqB,MAAmC;AAC/D,SACE,SAAS,yBACT,SAAS,2BACT,SAAS;AAEb;AAOA,SAAS,qBACP,OACA,YACA,WACQ;AACR,QAAM,KAAK,aAAa,YAAY,SAAS;AAC7C,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAAuB;AAAA,IAC3B;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM,GAAG,UAAU,IAAI,SAAS;AAAA,IAChC;AAAA,IACA;AAAA,IACA,eAAe;AAAA,EACjB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AASA,SAAS,2BAA2B,MAAmC;AACrE,SACE,SAAS,yBACT,SAAS,2BACT,SAAS;AAEb;AAQA,SAAS,2BACP,OACA,aACA,SACQ;AACR,QAAM,KAAK,mBAAmB,aAAa,OAAO;AAClD,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAA6B;AAAA,IACjC;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA,eAAe;AAAA,EACjB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAUA,SAAS,yBAAyB,QAAwB;AACxD,SAAO,GAAG,MAAM;AAClB;AAEA,SAAS,+BACP,OACA,QACA,aACQ;AACR,QAAM,KAAK,QAAQ,yBAAyB,MAAM,GAAG,WAAW;AAChE,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAAkB;AAAA,IACtB;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,yBAAyB,MAAM;AAAA,EACvC;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAaA,IAAM,yBAAyB;AAC/B,IAAM,2BAA2B,IAAI,KAAK;AAkB1C,IAAM,kBAAkB,oBAAI,IAAkC;AAE9D,SAAS,cAAc,SAAiB,QAAwB;AAC9D,SAAO,GAAG,OAAO,IAAI,MAAM;AAC7B;AAEA,SAAS,iBAAiB,MAAkB,KAAa,UAAiC;AACxF,MAAI,CAAC,KAAK,WAAW,CAAC,KAAK,OAAQ;AACnC,QAAM,MAAM,cAAc,KAAK,SAAS,KAAK,MAAM;AAGnD,kBAAgB,OAAO,GAAG;AAC1B,kBAAgB,IAAI,KAAK;AAAA,IACvB,SAAS,KAAK;AAAA,IACd,KAAK,KAAK,OAAO;AAAA,IACjB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,WAAW,MAAM;AAAA,EACnB,CAAC;AACD,SAAO,gBAAgB,OAAO,wBAAwB;AACpD,UAAM,SAAS,gBAAgB,KAAK,EAAE,KAAK,EAAE;AAC7C,QAAI,CAAC,OAAQ;AACb,oBAAgB,OAAO,MAAM;AAAA,EAC/B;AACF;AAEA,SAAS,iBACP,SACA,cACA,KAC8D;AAC9D,QAAM,QAAQ,gBAAgB,IAAI,cAAc,SAAS,YAAY,CAAC;AACtE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,aAAa,KAAK;AAC1B,oBAAgB,OAAO,cAAc,SAAS,YAAY,CAAC;AAC3D,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,KAAK,MAAM;AAAA,IACX,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,EACvD;AACF;AAmBA,SAAS,iBACP,OACA,MACA,KACe;AACf,QAAM,YAAY,UAAU,MAAM,GAAG;AACrC,MAAI,MAAM,QAAQ,SAAS,EAAG,QAAO;AACrC,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,YAAY,aAAa,MAAM,QAAQ,OAAO,EAAG,QAAO;AAE5D,MAAI,UAAyB;AAC7B,MAAI,eAA8B;AAClC,MAAI,WAA0B;AAC9B,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,QAAI,QAAS;AACb,UAAM,IAAI;AACV,QAAI,EAAE,SAASG,UAAS,YAAa;AACrC,UAAM,gBAAgB,EAAE,SAAS;AACjC,UAAM,iBAAiB,EAAE,UAAU,EAAE,QAAQ,SAAS,IAAI,IAAI;AAC9D,QAAI,CAAC,iBAAiB,CAAC,eAAgB;AACvC,UAAM,UAAU,EAAE,OAAO;AACzB,QAAI,YAAY,KAAK;AACnB,gBAAU;AACV;AAAA,IACF;AACA,QAAI,YAAY,aAAa,CAAC,aAAc,gBAAe;AAAA,aAClD,CAAC,SAAU,YAAW;AAAA,EACjC,CAAC;AACD,SAAO,WAAW,gBAAgB;AACpC;AAEO,SAAS,cAAc,MAAsB;AAClD,SAAO,WAAW,IAAI;AACxB;AAaO,SAAS,kBACd,OACA,aACA,KACQ;AACR,QAAM,KAAK,UAAU,aAAa,GAAG;AACrC,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAAoB;AAAA,IACxB;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM;AAAA,IACN,UAAU;AAAA,IACV,eAAe;AAAA,IACf,GAAI,QAAQ,YAAY,EAAE,IAAI,IAAI,CAAC;AAAA,EACrC;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAKA,SAAS,mBAAmB,OAAkB,MAAc,QAAwB;AAClF,QAAM,KAAK,WAAW,IAAI;AAC1B,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAAqB;AAAA,IACzB;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM;AAAA,IACN;AAAA,IACA,eAAe;AAAA,IACf,mBAAmB,CAAC;AAAA,IACpB;AAAA,IACA,eAAe;AAAA,EACjB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAYA,SAAS,wBACP,OACA,aACA,MACA,QACQ;AACR,QAAM,KAAK,gBAAgB,aAAa,IAAI;AAC5C,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,QAAM,OAAqB;AAAA,IACzB;AAAA,IACA,MAAMA,UAAS;AAAA,IACf;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,mBAAmB,CAAC;AAAA,IACpB,eAAe;AAAA,EACjB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAEA,SAAS,mBAAmB,OAAkB,MAAc,IAAoB;AAC9E,QAAM,KAAK,cAAc,IAAI;AAC7B,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,UAAM,WAAW,MAAM,kBAAkB,EAAE;AAC3C,UAAM,sBAAsB,IAAI,EAAE,GAAG,UAAU,cAAc,GAAG,CAAC;AACjE,WAAO;AAAA,EACT;AACA,QAAM,OAAqB;AAAA,IACzB;AAAA,IACA,MAAMA,UAAS;AAAA,IACf,MAAM;AAAA,IACN;AAAA,IACA,eAAe;AAAA,IACf,cAAc;AAAA,EAChB;AACA,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO;AACT;AAWO,SAAS,mBACd,OACA,MACA,QACA,QACA,IACA,UAAU,OACV,UACqB;AACrB,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AAE7D,QAAM,KAAK,mBAAmB,MAAM,QAAQ,MAAM;AAClD,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,UAAM,WAAW,MAAM,kBAAkB,EAAE;AAC3C,UAAM,gBAAgB,SAAS,QAAQ,aAAa,SAAS,aAAa,KAAK;AAC/E,UAAM,iBAAiB,SAAS,QAAQ,cAAc,MAAM,UAAU,IAAI;AAC1E,UAAM,YAAY;AAAA,MAChB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,IACrB;AAIA,UAAM,UAAqB;AAAA,MACzB,GAAG;AAAA,MACH,YAAYC,YAAW;AAAA,MACvB,cAAc;AAAA,MACd,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,YAAY,4BAA4B,SAAS;AAAA,IACnD;AACA,UAAM,sBAAsB,IAAI,OAAO;AACvC,WAAO,EAAE,MAAM,SAAS,SAAS,MAAM;AAAA,EACzC;AAEA,QAAM,SAAS;AAAA,IACb,WAAW;AAAA,IACX,YAAY,UAAU,IAAI;AAAA,IAC1B,mBAAmB;AAAA,EACrB;AACA,QAAM,OAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAYA,YAAW;AAAA,IACvB,YAAY,4BAA4B,MAAM;AAAA,IAC9C,cAAc;AAAA,IACd,WAAW;AAAA,IACX;AAAA;AAAA;AAAA,IAGA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,EACjC;AACA,QAAM,eAAe,IAAI,QAAQ,QAAQ,IAAI;AAC7C,SAAO,EAAE,MAAM,SAAS,KAAK;AAC/B;AAOA,SAAS,YAAY,OAAkB,iBAAyB,IAAkB;AAChF,MAAI,CAAC,MAAM,QAAQ,eAAe,EAAG;AAErC,QAAM,UAAU,oBAAI,IAAY,CAAC,eAAe,CAAC;AACjD,QAAM,QAA6C,CAAC,EAAE,QAAQ,iBAAiB,OAAO,EAAE,CAAC;AAEzF,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,MAAM;AACtC,QAAI,SAAS,iBAAkB;AAE/B,UAAM,WAAW,MAAM,cAAc,MAAM;AAC3C,eAAW,UAAU,UAAU;AAC7B,YAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,UAAI,KAAK,eAAeA,YAAW,UAAW;AAM9C,UAAI,CAAC,kBAAkB,IAAI,KAAK,IAAI,EAAG;AAMvC,UAAI,MAAM,QAAQ,eAAe,KAAK,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC,EAAG;AAExE,yBAAmB,OAAO,KAAK,MAAM,KAAK,QAAQ,KAAK,QAAQ,EAAE;AAEjE,UAAI,CAAC,QAAQ,IAAI,KAAK,MAAM,GAAG;AAC7B,gBAAQ,IAAI,KAAK,MAAM;AACvB,cAAM,KAAK,EAAE,QAAQ,KAAK,QAAQ,OAAO,QAAQ,EAAE,CAAC;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,mBACP,OACA,MACA,QACA,QACA,IACM;AACN,QAAM,KAAK,mBAAmB,MAAM,QAAQ,MAAM;AAClD,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,UAAM,WAAW,MAAM,kBAAkB,EAAE;AAC3C,UAAM,UAAqB,EAAE,GAAG,UAAU,cAAc,GAAG;AAC3D,UAAM,sBAAsB,IAAI,OAAO;AACvC;AAAA,EACF;AAEA,QAAM,OAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAYA,YAAW;AAAA,IACvB,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AACA,QAAM,eAAe,IAAI,QAAQ,QAAQ,IAAI;AAC/C;AAEA,eAAe,iBAAiB,KAAoB,IAA+B;AACjF,QAAMC,IAAG,MAAMC,MAAK,QAAQ,IAAI,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAChE,QAAMD,IAAG,WAAW,IAAI,YAAY,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM;AACvE;AAmBA,SAAS,qBACP,MACA,OACA,UACQ;AACR,QAAM,MAAM,UAAU,KAAK,SAAS,KAAK,GAAG;AAC5C,QAAM,cACJ,SAAS,MAAM,QAAQ,GAAG,IACrB,MAAM,kBAAkB,GAAG,IAC5B;AACN,QAAM,WAAW,iBAAiB,MAAM,aAAa,QAAQ;AAC7D,MAAI,UAAU;AACZ,UAAM,UAAU,QACZ,yBAAyB,OAAO,KAAK,SAAS,SAAS,OAAO,IAC9D,SAAS;AACb,WAAO,OAAO,KAAK,SAAS,OAAO;AAAA,EACrC;AACA,SAAO;AACT;AAuBA,SAAS,mBACP,OACoF;AACpF,QAAM,MAA0F,CAAC;AACjG,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,OAAO,MAAM,SAAU,KAAI,CAAC,IAAI,EAAE,SAAS;AAAA,QAC1C,KAAI,CAAC,IAAI;AAAA,EAChB;AACA,SAAO;AACT;AAEO,SAAS,2BACd,MACA,OACA,UACmB;AACnB,MAAI,KAAK,eAAe,EAAG,QAAO;AAClC,QAAM,KAAK,KAAK,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AACvD,QAAM,QAAQ,mBAAmB,KAAK,UAAU;AAChD,SAAO;AAAA,IACL,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM;AAAA,IAClC,WAAW;AAAA,IACX,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,cAAc,gBAAgB,IAAI;AAAA,IAClC,GAAI,KAAK,WAAW,OAAO,EAAE,eAAe,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,IACrE,GAAI,KAAK,WAAW,aAChB,EAAE,qBAAqB,KAAK,UAAU,WAAW,IACjD,CAAC;AAAA,IACL,GAAI,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,MAAM,IAAI,CAAC;AAAA,IAC7D,cAAc,qBAAqB,MAAM,OAAO,QAAQ;AAAA,EAC1D;AACF;AAIO,SAAS,oBACd,YACA,OACA,UACqC;AACrC,SAAO,OAAO,SAAS;AACrB,UAAM,KAAK,2BAA2B,MAAM,OAAO,QAAQ;AAC3D,QAAI,CAAC,GAAI;AACT,UAAMA,IAAG,MAAMC,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,UAAMD,IAAG,WAAW,YAAY,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM;AAAA,EACnE;AACF;AAQA,eAAe,8BACb,KACA,MACA,cACA,WACA,YACA,OACA,gBACe;AACf,QAAM,QAAQ,mBAAmB,KAAK,UAAU;AAChD,QAAM,QAAQ,kBAAkB;AAChC,QAAM,OAAO,YAAY,IAAI;AAC7B,QAAM,UACJ,QAAQ,IACJ,GAAG,KAAK,qBAAqB,UAAU,gBACtC,OAAO,OAAO,IAAI,KAAK,MACxB,QAAQ,UAAU,eAAe,OAAO,SAAS,IAAI,KAAK;AAChE,QAAM,KAAiB;AAAA,IACrB,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM;AAAA,IAClC;AAAA,IACA,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,WAAW;AAAA,IACX,cAAc;AAAA,IACd,GAAI,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,MAAM,IAAI,CAAC;AAAA,IAC7D;AAAA,IACA,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,eAAe;AAAA,EACjB;AACA,QAAM,iBAAiB,KAAK,EAAE;AAChC;AAYA,eAAe,wBACb,KACA,MACA,IACe;AACf,QAAM,QAAQ,mBAAmB,KAAK,UAAU;AAChD,QAAM,KAAiB;AAAA,IACrB,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM;AAAA,IAClC,WAAW;AAAA,IACX,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,cAAc,gBAAgB,IAAI;AAAA,IAClC,GAAI,KAAK,WAAW,OAAO,EAAE,eAAe,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,IACrE,GAAI,KAAK,WAAW,aAChB,EAAE,qBAAqB,KAAK,UAAU,WAAW,IACjD,CAAC;AAAA,IACL,GAAI,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,MAAM,IAAI,CAAC;AAAA,IAC7D,cAAc,qBAAqB,MAAM,IAAI,OAAO,IAAI,QAAQ;AAAA,EAClE;AACA,QAAM,iBAAiB,KAAK,EAAE;AAChC;AAOA,eAAe,gBACb,KACA,MACA,cACA,IACA,OACA,QACe;AACf,QAAM,EAAE,WAAW,SAAS,IAAI,8BAA8B;AAC9D,MAAI,CAAC,IAAI,WAAY,KAAI,aAAa,oBAAI,IAAI;AAC9C,QAAM,OAAO,YAAY,IAAI,KAAK,KAAK;AACvC,QAAM,MAAM,GAAG,KAAK,OAAO,KAAK,IAAI;AACpC,QAAM,WAAW,IAAI,WAAW,IAAI,GAAG;AACvC,MAAI;AACJ,MAAI,YAAY,QAAQ,SAAS,UAAU,UAAU;AACnD,aAAS,SAAS;AAClB,aAAS,SAAS;AAClB,aAAS,SAAS;AAClB,aAAS,MAAM,IAAI,SAAS,SAAS,MAAM,IAAI,MAAM,KAAK,KAAK,CAAC;AAChE,YAAQ;AAAA,EACV,OAAO;AACL,YAAQ;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,OAAO,oBAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,IAC9B;AACA,QAAI,WAAW,IAAI,KAAK,KAAK;AAAA,EAC/B;AAEA,MAAI,MAAM,QAAQ,UAAW;AAK7B,MAAI,WAAW;AACf,MAAI,MAAM;AACV,aAAW,CAAC,MAAM,CAAC,KAAK,MAAM,OAAO;AACnC,QAAI,IAAI,KAAK;AACX,YAAM;AACN,iBAAW;AAAA,IACb;AAAA,EACF;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACA,MAAI,WAAW,OAAO,GAAG;AAC3B;AAEA,eAAsB,WAAW,KAAoB,MAAiC;AAKpF,QAAM,KAAK,KAAK,gBAAgB,OAAO,GAAG;AAC1C,QAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI;AAI7C,QAAM,MAAM,KAAK,OAAO;AAKxB,MAAI,KAAK,+BAA+B,OAAO;AAC7C,yBAAqB,IAAI,WAAW,eAAe;AAAA,EACrD;AAKA,QAAM,WAAW,kBAAkB,IAAI,OAAO,KAAK,SAAS,GAAG;AAC/D,QAAM,UAAU,KAAK,eAAe;AAQpC,QAAM,oBAAoB,IAAI,MAAM,kBAAkB,QAAQ;AAC9D,QAAM,WAAW,iBAAiB,MAAM,mBAAmB,IAAI,QAAQ;AAMvE,mBAAiB,MAAM,OAAO,QAAQ;AACtC,QAAM,iBAAiB,MACrB,WAAW,uBAAuB,IAAI,OAAO,KAAK,SAAS,UAAU,QAAQ,IAAI;AAOnF,QAAM,mBAAgE,WAClE;AAAA,IACE,MAAM,yBAAyB,IAAI,OAAO,KAAK,SAAS,SAAS,OAAO;AAAA,IACxE,GAAI,SAAS,SAAS,SAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC;AAAA,EAC/D,IACA;AAEJ,MAAI,eAAe;AAQnB,QAAM,sBAAsB,sBAAsB,KAAK,IAAI;AAE3D,MAAI,KAAK,UAAU;AAOjB,QAAI,qBAAqB;AACvB,YAAM,OAAO,YAAY,IAAI;AAG7B,UAAI;AACJ,UAAI,MAAM;AACR,2BAAmB,IAAI,OAAO,MAAM,KAAK,QAAQ;AACjD,mBAAW,WAAW,IAAI;AAAA,MAC5B,OAAO;AAGL,cAAM,YAAY,KAAK,UAAU,KAAK;AACtC,mBAAW;AAAA,UACT,IAAI;AAAA,UACJ,KAAK;AAAA,UACL;AAAA,UACA,KAAK;AAAA,QACP;AAAA,MACF;AACA,YAAM,SAAS;AAAA,QACb,IAAI;AAAA,QACJE,UAAS;AAAA,QACT,eAAe;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,OAAQ,gBAAe;AAAA,IAC7B;AAAA,EACF,WACE,KAAK,mBACL,KAAK,wBACL,uBAAuB,KAAK,IAAI,GAChC;AAaA,UAAM,WAAW;AAAA,MACf,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,UAAM,WACJ,KAAK,SAAS,0BACVA,UAAS,gBACTA,UAAS;AACf,UAAM,SAAS;AAAA,MACb,IAAI;AAAA,MACJ;AAAA,MACA,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,OAAQ,gBAAe;AAAA,EAC7B,WACE,KAAK,wBACL,KAAK,wBACL,2BAA2B,KAAK,IAAI,GACpC;AAiBA,UAAM,WAAW;AAAA,MACf,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,UAAM,SAAS;AAAA,MACb,IAAI;AAAA,MACJA,UAAS;AAAA,MACT,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,OAAQ,gBAAe;AAAA,EAC7B,WACE,KAAK,cAAc,UACnB,KAAK,cACL,KAAK,aACL,qBAAqB,KAAK,IAAI,GAC9B;AAqBA,UAAM,WAAW,qBAAqB,IAAI,OAAO,KAAK,YAAY,KAAK,SAAS;AAChF,UAAM,SAAS;AAAA,MACb,IAAI;AAAA,MACJA,UAAS;AAAA,MACT,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,OAAQ,gBAAe;AAAA,EAC7B,WAAW,KAAK,oBAAoB,2BAA2B,KAAK,IAAI,GAAG;AAwBzE,UAAM,WAAW;AAAA,MACf,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,UAAM,SAAS;AAAA,MACb,IAAI;AAAA,MACJA,UAAS;AAAA,MACT,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,OAAQ,gBAAe;AAAA,EAC7B,OAAO;AAgBL,UAAM,OAAO,YAAY,IAAI;AAC7B,QAAI,qBAAqB;AACzB,QAAI,uBAAuB,QAAQ,SAAS,KAAK,WAAW,CAAC,eAAe,IAAI,GAAG;AACjF,YAAM,WAAW,iBAAiB,IAAI,OAAO,MAAM,GAAG;AACtD,UAAI,YAAY,aAAa,UAAU;AACrC;AAAA,UACE,IAAI;AAAA,UACJA,UAAS;AAAA,UACT,eAAe;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,uBAAe;AACf,6BAAqB;AAAA,MACvB,WAAW,CAAC,UAAU;AACpB,cAAM,iBAAiB,mBAAmB,IAAI,OAAO,MAAM,EAAE;AAC7D;AAAA,UACE,IAAI;AAAA,UACJA,UAAS;AAAA,UACT,eAAe;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,uBAAe;AACf,6BAAqB;AAAA,MACvB;AAAA,IACF;AAQA,QAAI,CAAC,sBAAsB,KAAK,cAAc;AAC5C,YAAM,SAAS,iBAAiB,KAAK,SAAS,KAAK,cAAc,KAAK;AACtE,UAAI,UAAU,OAAO,YAAY,KAAK,SAAS;AAC7C,cAAM,WAAW,kBAAkB,IAAI,OAAO,OAAO,SAAS,OAAO,GAAG;AAKxE,cAAM,iBAAiB,OAAO,WAC1B,uBAAuB,IAAI,OAAO,OAAO,SAAS,UAAU,OAAO,QAAQ,IAC3E;AACJ,cAAM,mBACJ,OAAO,WACH;AAAA,UACE,MAAM;AAAA,YACJ,IAAI;AAAA,YACJ,OAAO;AAAA,YACP,OAAO,SAAS;AAAA,UAClB;AAAA,UACA,GAAI,OAAO,SAAS,SAAS,SACzB,EAAE,MAAM,OAAO,SAAS,KAAK,IAC7B,CAAC;AAAA,QACP,IACA;AACN;AAAA,UACE,IAAI;AAAA,UACJA,UAAS;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,KAAK,eAAe,GAAG;AACzB,gBAAY,IAAI,OAAO,UAAU,EAAE;AAQnC,QAAI,IAAI,0BAA0B,OAAO;AACvC,YAAM,QAAQ,mBAAmB,KAAK,UAAU;AAChD,YAAM,KAAiB;AAAA,QACrB,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM;AAAA,QAClC,WAAW;AAAA,QACX,SAAS,KAAK;AAAA,QACd,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK;AAAA,QACb,cAAc,gBAAgB,IAAI;AAAA,QAClC,GAAI,KAAK,WAAW,OAAO,EAAE,eAAe,KAAK,UAAU,KAAK,IAAI,CAAC;AAAA,QACrE,GAAI,KAAK,WAAW,aAChB,EAAE,qBAAqB,KAAK,UAAU,WAAW,IACjD,CAAC;AAAA,QACL,GAAI,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,MAAM,IAAI,CAAC;AAAA,QAC7D;AAAA,MACF;AACA,YAAM,iBAAiB,KAAK,EAAE;AAAA,IAChC;AAAA,EACF;AAiBA,MAAI,KAAK,eAAe,GAAG;AACzB,UAAM,SAAS,mBAAmB,IAAI;AAStC,QAAI,KAAK,WAAW;AAClB,YAAM,wBAAwB,KAAK,MAAM,EAAE;AAAA,IAC7C,WAAW,WAAW,UAAa,UAAU,KAAK;AAOhD,YAAM,8BAA8B,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC;AAAA,IACxE,WACE,WAAW,UACX,UAAU,OACV,sBAAsB,KAAK,IAAI,GAC/B;AACA,YAAM,gBAAgB,KAAK,MAAM,UAAU,IAAI,OAAO,MAAM;AAAA,IAC9D;AAAA,EACF;AACA,OAAK;AAIL,MAAI,IAAI,gBAAiB,OAAM,IAAI,gBAAgB,IAAI,KAAK;AAC9D;AAuBO,SAAS,qBACd,OACA,OAA+B,CAAC,GACxB;AACR,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,SAASC,UAAS,YAAa;AACrC,eAAW,IAAI,EAAE,MAAM,EAAE;AACzB,QAAI,EAAE,SAAS;AACb,iBAAW,SAAS,EAAE,QAAS,YAAW,IAAI,OAAO,EAAE;AAAA,IACzD;AAAA,EACF,CAAC;AAED,QAAM,YAAyD,CAAC;AAChE,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,SAASA,UAAS,aAAc;AACtC,UAAM,SAAS,WAAW,IAAI,EAAE,IAAI;AACpC,QAAI,CAAC,OAAQ;AACb,QAAI,WAAW,GAAI;AACnB,cAAU,KAAK,EAAE,YAAY,IAAI,WAAW,OAAO,CAAC;AAAA,EACtD,CAAC;AAED,MAAI,WAAW;AACf,aAAW,EAAE,YAAAC,aAAY,WAAAC,WAAU,KAAK,WAAW;AACjD,QAAI,KAAK,YAAY,KAAK,SAAS,SAAS,KAAK,KAAK,WAAW;AAC/D,YAAM,OAAO,mBAAmB,OAAOD,aAAY,KAAK,UAAU,KAAK,SAAS;AAChF,UAAI,CAAC,KAAK,SAAS;AAIjB;AAAA,MACF;AAAA,IACF;AACA,wBAAoB,OAAOA,aAAYC,UAAS;AAChD,UAAM,SAASD,WAAU;AACzB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAkBA,aAAoBC,YAAyB;AAC1F,QAAM,UAAU,CAAC,GAAG,MAAM,aAAaD,WAAU,CAAC;AAClD,QAAM,WAAW,CAAC,GAAG,MAAM,cAAcA,WAAU,CAAC;AAEpD,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,gBAAY,OAAO,MAAM,KAAK,QAAQC,YAAW,MAAM;AAAA,EACzD;AACA,aAAW,UAAU,UAAU;AAC7B,UAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,gBAAY,OAAO,MAAMA,YAAW,KAAK,QAAQ,MAAM;AAAA,EACzD;AACF;AAEA,SAAS,YACP,OACA,MACA,WACA,WACA,WACM;AACN,QAAM,SAAS,SAAS;AAIxB,QAAM,QACJ,KAAK,eAAeC,YAAW,WAC3B,eAAe,WAAW,WAAW,KAAK,IAAI,IAC9C,KAAK,eAAeA,YAAW,WAC7B,eAAe,WAAW,WAAW,KAAK,IAAI,IAC9C,gBAAgB,WAAW,WAAW,KAAK,IAAI;AAEvD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,WAAW,MAAM,kBAAkB,KAAK;AAC9C,UAAM,SAAoB;AAAA,MACxB,GAAG;AAAA,MACH,YAAY,SAAS,aAAa,MAAM,KAAK,aAAa;AAAA,MAC1D,cAAc,UAAU,SAAS,cAAc,KAAK,YAAY;AAAA,IAClE;AACA,UAAM,sBAAsB,OAAO,MAAM;AACzC;AAAA,EACF;AAEA,QAAM,UAAqB;AAAA,IACzB,GAAG;AAAA,IACH,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,QAAM,eAAe,OAAO,WAAW,WAAW,OAAO;AAC3D;AAEA,SAAS,UAAU,GAAuB,GAA2C;AACnF,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,IAAI,KAAK,CAAC,EAAE,QAAQ,KAAK,IAAI,KAAK,CAAC,EAAE,QAAQ,IAAI,IAAI;AAC9D;AAEO,SAAS,gBAAgB,KAAyD;AACvF,SAAO,CAAC,SAAS,WAAW,KAAK,IAAI;AACvC;AAoBA,eAAsB,eACpB,OACA,UAA4B,CAAC,GACqB;AAClD,QAAM,aAAa,QAAQ,cAAc,2BAA2B;AACpE,QAAM,MAAM,QAAQ,OAAO,KAAK,IAAI;AACpC,QAAM,SAAuB,CAAC;AAE9B,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,eAAeA,YAAW,SAAU;AAC1C,QAAI,CAAC,EAAE,aAAc;AACrB,UAAM,YAAY,qBAAqB,EAAE,MAAM,UAAU;AACzD,UAAM,MAAM,MAAM,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ;AACnD,QAAI,MAAM,WAAW;AACnB,YAAM,UAAqB,EAAE,GAAG,GAAG,YAAYA,YAAW,OAAO,YAAY,IAAI;AACjF,YAAM,sBAAsB,IAAI,OAAO;AACvC,aAAO,KAAK;AAAA,QACV,QAAQ;AAAA,QACR,QAAQ,EAAE;AAAA,QACV,QAAQ,EAAE;AAAA,QACV,UAAU,EAAE;AAAA,QACZ,aAAa;AAAA,QACb,OAAO;AAAA,QACP,cAAc,EAAE;AAAA,QAChB,gBAAgB,IAAI,KAAK,GAAG,EAAE,YAAY;AAAA,MAC5C,CAAC;AAKD,oBAAc;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,QACA,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,MAAMA,YAAW;AAAA,UACjB,IAAIA,YAAW;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,MAAI,QAAQ,mBAAmB,OAAO,SAAS,GAAG;AAChD,UAAM,kBAAkB,QAAQ,iBAAiB,MAAM;AAAA,EACzD;AAEA,SAAO,EAAE,OAAO,OAAO,QAAQ,OAAO;AACxC;AAEA,eAAe,kBAAkB,iBAAyB,QAAqC;AAC7F,QAAMC,IAAG,MAAMC,MAAK,QAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;AACjE,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAChE,QAAMD,IAAG,WAAW,iBAAiB,OAAO,MAAM;AACpD;AAEA,eAAsB,gBAAgB,iBAAgD;AACpF,MAAI;AACF,UAAM,MAAM,MAAMA,IAAG,SAAS,iBAAiB,MAAM;AACrD,WAAO,IACJ,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAe;AAAA,EACjD,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAcO,SAAS,mBACd,OACA,UAAgC,CAAC,GACrB;AACZ,MAAI,UAAU;AACd,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,OAAO,MAAY;AACvB,QAAI,QAAS;AACb,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,eAAe,OAAO;AAAA,UAC1B,YAAY,QAAQ;AAAA,UACpB,iBAAiB,QAAQ;AAAA,UACzB,SAAS,QAAQ;AAAA,QACnB,CAAC;AACD,YAAI,QAAQ,gBAAiB,OAAM,QAAQ,gBAAgB,KAAK;AAAA,MAClE,SAAS,KAAK;AACZ,gBAAQ,MAAM,yBAAyB,GAAG;AAAA,MAC5C;AAAA,IACF,GAAG;AAAA,EACL;AACA,QAAM,WAAW,YAAY,MAAM,UAAU;AAC7C,MAAI,OAAO,SAAS,UAAU,WAAY,UAAS,MAAM;AACzD,SAAO,MAAM;AACX,cAAU;AACV,kBAAc,QAAQ;AAAA,EACxB;AACF;AAEA,eAAsB,gBAAgB,YAA2C;AAC/E,MAAI;AACF,UAAM,MAAM,MAAMA,IAAG,SAAS,YAAY,MAAM;AAChD,UAAM,SAAS,IACZ,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAe;AAC/C,WAAO,gBAAgB,MAAM;AAAA,EAC/B,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;AAAA,EACR;AACF;AAWA,SAAS,0BAA0B,IAAyB;AAC1D,MAAI,GAAG,iBAAiB,GAAG,oBAAqB,QAAO;AACvD,MAAI,GAAG,UAAW,QAAO;AACzB,MAAI,CAAC,GAAG,WAAY,QAAO;AAC3B,QAAM,QAAQ,4BAA4B,GAAG,UAAU;AACvD,SAAO,UAAU,UAAa,UAAU,GAAG;AAC7C;AAwBA,SAAS,gBAAgB,QAAoC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAqB,CAAC;AAC5B,aAAW,MAAM,QAAQ;AACvB,UAAM,MACJ,GAAG,OACF,GAAG,WAAW,GAAG,SAAS,GAAG,GAAG,OAAO,IAAI,GAAG,MAAM,KAAK;AAC5D,QAAI,QAAQ,QAAW;AACrB,WAAK,KAAK,EAAE;AACZ;AAAA,IACF;AACA,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,SAAK,KAAK,EAAE;AAAA,EACd;AAEA,QAAM,WAAW,CAAC,OAA2B,GAAG,GAAG,OAAO,KAAS,GAAG,YAAY;AAClF,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,MAAM,MAAM;AACrB,QAAI,GAAG,WAAW,CAAC,0BAA0B,EAAE,EAAG,gBAAe,IAAI,SAAS,EAAE,CAAC;AAAA,EACnF;AACA,SAAO,KAAK,OAAO,CAAC,OAAO;AACzB,QAAI,CAAC,GAAG,WAAW,CAAC,0BAA0B,EAAE,EAAG,QAAO;AAC1D,WAAO,CAAC,eAAe,IAAI,SAAS,EAAE,CAAC;AAAA,EACzC,CAAC;AACH;AAuBO,SAAS,cACd,OACA,UACqB;AACrB,QAAM,WAAW,SAAS;AAK1B,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,QAAQ,SAAS,SAAS,CAAC,GAAG;AACvC,QAAI,MAAM,QAAQ,KAAK,GAAG,EAAG;AAC7B,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,QAAQ,KAAK,KAAK,KAAK,UAAU;AACvC;AAAA,EACF;AAEA,aAAW,QAAQ,SAAS,SAAS,CAAC,GAAG;AACvC,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AACZ,UAAM,KAAK,KAAK,OAAO,MAAM;AAC7B,QAAI,CAAC,GAAI;AACT,QAAI,MAAM,QAAQ,EAAE,EAAG;AAIvB,QAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,MAAM,QAAQ,KAAK,MAAM,EAAG;AAChE,UAAM,eAAe,IAAI,KAAK,QAAQ,KAAK,QAAQ,KAAK;AACxD;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AGjxEA,SAAS,YAAYE,WAAU;AAC/B,OAAOC,WAAU;AAqBjB,IAAM,OAA0B,CAAC;AAK1B,SAAS,sBACd,UACA,MACA,KACM;AACN,QAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,OAAK,KAAK;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC3B,QAAQ;AAAA,EACV,CAAC;AAGD,UAAQ,KAAK,UAAU,QAAQ,YAAY,IAAI,KAAK,EAAE,OAAO,EAAE;AACjE;AAMO,SAAS,wBAA2C;AACzD,SAAO,KAAK,OAAO,GAAG,KAAK,MAAM;AACnC;AAaA,eAAsB,sBACpB,QACA,YACe;AACf,MAAI,OAAO,WAAW,EAAG;AACzB,QAAMC,IAAG,MAAMC,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAChE,QAAMD,IAAG,WAAW,YAAY,OAAO,MAAM;AAC/C;AAMO,SAAS,4BAAqC;AACnD,QAAM,MAAM,QAAQ,IAAI;AACxB,SAAO,QAAQ,OAAO,QAAQ;AAChC;AAIO,SAAS,uBAAuB,OAAuB;AAC5D,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO,UAAU,KAAK;AACxB;AAsBA,IAAM,cAAsC,CAAC;AAEtC,SAAS,qBAAqB,MAAkC;AACrE,cAAY,KAAK,IAAI;AACvB;AAEO,SAAS,wBAAgD;AAC9D,SAAO,YAAY,OAAO,GAAG,YAAY,MAAM;AACjD;AAMO,SAAS,uBAAgC;AAC9C,QAAM,MAAM,QAAQ,IAAI;AACxB,SAAO,QAAQ,OAAO,QAAQ;AAChC;AAEA,eAAsB,uBACpB,OACA,cACe;AACf,MAAI,MAAM,WAAW,EAAG;AACxB,QAAME,IAAG,MAAMC,MAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,QAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,KAAK,UAAU,EAAE,GAAG,GAAG,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AACpG,QAAMD,IAAG,WAAW,cAAc,OAAO,MAAM;AACjD;AAEO,SAAS,2BAA2B,OAAuB;AAChE,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO,UAAU,KAAK;AACxB;;;AC7JA,SAAS,YAAYE,WAAU;AAC/B,OAAOC,WAAU;AACjB,OAAO,YAA6B;AACpC,SAAS,aAAAC,kBAAiB;AAE1B,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;;;ACLpC,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AACjB,SAAS,SAAS,iBAAiB;AAoRnC,SAA4B,mBAAnBC,wBAAqC;AAnQvC,IAAM,0BAA0B,oBAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AACrF,IAAM,yBAAyB,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AACxD,IAAM,eAAe,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AACF,CAAC;AAOD,IAAM,sBAAsB,oBAAI,IAAqB;AAErD,eAAsB,gBAAgB,KAA+B;AACnE,QAAM,SAAS,oBAAoB,IAAI,GAAG;AAC1C,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI;AACF,UAAM,OAAO,MAAMF,IAAG,KAAKC,MAAK,KAAK,KAAK,YAAY,CAAC;AACvD,UAAM,KAAK,KAAK,OAAO;AACvB,wBAAoB,IAAI,KAAK,EAAE;AAC/B,WAAO;AAAA,EACT,QAAQ;AACN,wBAAoB,IAAI,KAAK,KAAK;AAClC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,MAAoD;AAC/E,QAAM,MAAMA,MAAK,QAAQ,IAAI;AAC7B,MAAI,uBAAuB,IAAI,GAAG,EAAG,QAAO,EAAE,OAAO,MAAM,UAAU,IAAI,MAAM,CAAC,EAAE;AAMlF,MAAI,SAAS,UAAU,KAAK,WAAW,OAAO,GAAG;AAC/C,QAAI,kBAAkB,IAAI,EAAG,QAAO,EAAE,OAAO,OAAO,UAAU,GAAG;AAKjE,QAAI,sBAAsB,IAAI,EAAG,QAAO,EAAE,OAAO,OAAO,UAAU,GAAG;AACrE,WAAO,EAAE,OAAO,MAAM,UAAU,MAAM;AAAA,EACxC;AACA,SAAO,EAAE,OAAO,OAAO,UAAU,GAAG;AACtC;AAWO,SAAS,sBAAsB,MAAuB;AAC3D,SAAO,SAAS;AAClB;AAEO,SAAS,yBAAyB,MAAuB;AAC9D,SAAO,sCAAsC,KAAK,IAAI;AACxD;AAeO,SAAS,WAAW,UAA2B;AACpD,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,QAAM,WAAW,WAAW,MAAM,GAAG;AACrC,aAAW,OAAO,UAAU;AAC1B,QAAI,QAAQ,eAAe,QAAQ,kBAAkB,QAAQ,qBAAqB;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,OAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAC9C,SAAO,4CAA4C,KAAK,IAAI;AAC9D;AAOO,SAAS,kBAAkB,MAAuB;AACvD,MACE,SAAS,mBACT,SAAS,kBACT,SAAS,eACT;AACA,WAAO;AAAA,EACT;AAEA,SAAO,+CAA+C,KAAK,IAAI;AACjE;AAUO,SAAS,qBAAqB,KAAqB;AACxD,QAAM,MAAM,IAAI;AAChB,QAAM,MAAgB,IAAI,MAAM,GAAG;AACnC,MAAI,IAAI;AAER,MAAI,WAAuB;AAC3B,MAAI,UAAU;AACd,SAAO,IAAI,KAAK;AACd,UAAM,IAAI,IAAI,CAAC;AACf,QAAI,aAAa,GAAG;AAClB,UAAI,CAAC,IAAI;AACT,UAAI,SAAS;AACX,kBAAU;AAAA,MACZ,WAAW,MAAM,MAAM;AACrB,kBAAU;AAAA,MACZ,WAAW,MAAM,UAAU;AACzB,mBAAW;AAAA,MACb;AACA;AACA;AAAA,IACF;AACA,QAAI,MAAM,OAAO,IAAI,IAAI,KAAK;AAC5B,YAAM,OAAO,IAAI,IAAI,CAAC;AACtB,UAAI,SAAS,KAAK;AAChB,YAAI,CAAC,IAAI;AACT,YAAI,IAAI,CAAC,IAAI;AACb,YAAI,IAAI,IAAI;AACZ,eAAO,IAAI,OAAO,IAAI,CAAC,MAAM,MAAM;AACjC,cAAI,CAAC,IAAI;AACT;AAAA,QACF;AACA,YAAI;AACJ;AAAA,MACF;AACA,UAAI,SAAS,KAAK;AAChB,YAAI,CAAC,IAAI;AACT,YAAI,IAAI,CAAC,IAAI;AACb,YAAI,IAAI,IAAI;AACZ,eAAO,IAAI,KAAK;AACd,cAAI,IAAI,CAAC,MAAM,MAAM;AACnB,gBAAI,CAAC,IAAI;AACT;AACA;AAAA,UACF;AACA,cAAI,IAAI,CAAC,MAAM,OAAO,IAAI,IAAI,OAAO,IAAI,IAAI,CAAC,MAAM,KAAK;AACvD,gBAAI,CAAC,IAAI;AACT,gBAAI,IAAI,CAAC,IAAI;AACb,iBAAK;AACL;AAAA,UACF;AACA,cAAI,CAAC,IAAI;AACT;AAAA,QACF;AACA,YAAI;AACJ;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,IAAI;AACT,QAAI,MAAM,OAAO,MAAM,OAAO,MAAM,IAAK,YAAW;AACpD;AAAA,EACF;AACA,SAAO,IAAI,KAAK,EAAE;AACpB;AAYA,IAAM,WAAW;AAEV,SAAS,eAAe,WAAmB,MAAuB;AACvE,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,EAAG,QAAO;AAIpE,MAAI,CAAC,SAAS,KAAK,SAAS,EAAG,QAAO;AACtC,QAAM,CAAC,YAAY,UAAU,IAAI,KAAK,MAAM,GAAG;AAC/C,MAAI;AACJ,MAAI;AAEF,UAAM,YAAY,UAAU,WAAW,IAAI,IAAI,QAAQ,SAAS,KAAK;AACrE,aAAS,IAAI,IAAI,SAAS;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,SAAS,YAAY,OAAO,cAAc,IAAI,YAAY,EAAG,QAAO;AAC/E,MAAI,cAAc,OAAO,SAAS,WAAY,QAAO;AACrD,SAAO;AACT;AAKO,SAAS,aAAa,KAA6C;AACxE,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,QAAQ,iBAAiB,EAAE,EAAE,KAAK,KAAK;AACpD;AAEA,eAAsB,SAAY,UAA8B;AAC9D,QAAM,MAAM,MAAMD,IAAG,SAAS,UAAU,MAAM;AAC9C,SAAO,KAAK,MAAM,GAAG;AACvB;AAEA,eAAsB,SAAY,UAA8B;AAC9D,QAAM,MAAM,MAAMA,IAAG,SAAS,UAAU,MAAM;AAC9C,SAAO,UAAU,GAAG;AACtB;AAEA,eAAsB,OAAO,GAA6B;AACxD,MAAI;AACF,UAAMA,IAAG,OAAO,CAAC;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjRA,SAAS,YAAYG,WAAU;AAC/B,OAAOC,WAAU;AACjB,SAAS,SAAS,iBAAiB;AAQnC,IAAM,mBAAmB;AAEzB,SAAS,qBAAqB,SAAyC;AACrE,QAAM,MAA8B,CAAC;AACrC,aAAW,WAAW,QAAQ,MAAM,IAAI,GAAG;AACzC,UAAM,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACzC,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,WAAW,GAAG,EAAG;AAC1B,UAAM,QAAQ,iBAAiB,KAAK,IAAI;AACxC,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM,CAAC,EAAG,YAAY;AACnC,UAAM,UAAU,MAAM,CAAC,KAAK;AAC5B,QAAI,IAAI,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAiBA,SAAS,kBAAkB,WAAkD;AAC3E,QAAM,MAA8B,CAAC;AAGrC,aAAW,SAAS,UAAU,SAAS,gBAAgB,CAAC,GAAG;AACzD,UAAM,QAAQ,iBAAiB,KAAK,KAAK;AACzC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,CAAC,EAAG,YAAY,CAAC,IAAI,MAAM,CAAC,KAAK;AAAA,EAC7C;AAGA,QAAM,aAAa,UAAU,MAAM,QAAQ,gBAAgB,CAAC;AAC5D,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACtD,QAAI,KAAK,YAAY,MAAM,SAAU;AACrC,UAAM,MAAM,OAAO,UAAU,WAAW,QAAS,OAAO,WAAW;AACnE,QAAI,KAAK,YAAY,CAAC,IAAI,IAAI,QAAQ,iBAAiB,EAAE;AAAA,EAC3D;AACA,SAAO;AACT;AAWA,eAAsB,sBAAsB,YAAmD;AAC7F,QAAM,gBAAgBC,MAAK,KAAK,YAAY,gBAAgB;AAC5D,QAAM,mBAAmBA,MAAK,KAAK,YAAY,kBAAkB;AACjE,QAAM,YAAYA,MAAK,KAAK,YAAY,UAAU;AAElD,QAAM,eAAe,MAAM,OAAO,aAAa;AAC/C,QAAM,kBAAkB,MAAM,OAAO,gBAAgB;AACrD,QAAM,WAAW,MAAM,OAAO,SAAS;AACvC,MAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,SAAU,QAAO;AAE3D,MAAI,OAAOA,MAAK,SAAS,UAAU;AACnC,MAAI;AACJ,QAAM,eAAuC,CAAC;AAE9C,MAAI,cAAc;AAChB,UAAM,MAAM,MAAMC,IAAG,SAAS,eAAe,MAAM;AACnD,UAAM,YAAY,UAAU,GAAG;AAC/B,WAAO,UAAU,SAAS,QAAQ,UAAU,MAAM,QAAQ,QAAQ;AAClE,cAAU,UAAU,SAAS,WAAW,UAAU,MAAM,QAAQ,WAAW;AAC3E,WAAO,OAAO,cAAc,kBAAkB,SAAS,CAAC;AAAA,EAC1D;AAEA,MAAI,iBAAiB;AACnB,UAAM,MAAM,MAAMA,IAAG,SAAS,kBAAkB,MAAM;AACtD,WAAO,OAAO,cAAc,qBAAqB,GAAG,CAAC;AAAA,EACvD;AAEA,SAAO,EAAE,MAAM,SAAS,aAAa;AACvC;AAKO,SAAS,gBAAgB,SAAqC;AACnE,SAAO;AAAA,IACL,MAAM,QAAQ;AAAA,IACd,SAAS,QAAQ;AAAA,IACjB,cAAc,QAAQ;AAAA,EACxB;AACF;;;AC9GA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AACjB,SAAS,iBAAiB;AAkB1B,eAAsB,eAAe,UAAkD;AACrF,QAAM,aAAa;AAAA,IACjBC,MAAK,KAAK,UAAU,YAAY;AAAA,IAChCA,MAAK,KAAK,UAAU,WAAW,YAAY;AAAA,EAC7C;AACA,aAAW,QAAQ,YAAY;AAC7B,QAAI,MAAM,OAAO,IAAI,GAAG;AACtB,YAAM,MAAM,MAAMC,IAAG,SAAS,MAAM,MAAM;AAC1C,aAAO,gBAAgB,GAAG;AAAA,IAC5B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,KAA6B;AACpD,QAAM,QAA0B,CAAC;AACjC,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,QAAQ,iBAAiB,KAAK,OAAO;AAC3C,QAAI,CAAC,MAAO;AACZ,UAAM,KAAK,EAAE,SAAS,MAAM,CAAC,GAAI,QAAQ,MAAM,CAAC,EAAG,KAAK,EAAE,CAAC;AAAA,EAC7D;AACA,SAAO,EAAE,MAAM;AACjB;AAKO,SAAS,WAAW,MAAsB,UAAiC;AAChF,QAAM,aAAa,SAAS,MAAMD,MAAK,GAAG,EAAE,KAAK,GAAG;AACpD,aAAW,QAAQ,KAAK,OAAO;AAC7B,QAAI,eAAe,KAAK,SAAS,UAAU,EAAG,QAAO,KAAK;AAAA,EAC5D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,YAAoB,UAA2B;AACrE,MAAI,UAAU,WAAW,WAAW,GAAG,IAAI,WAAW,MAAM,CAAC,IAAI;AACjE,MAAI,YAAY,IAAK,QAAO,CAAC,SAAS,SAAS,GAAG;AAClD,MAAI,YAAY,QAAQ,YAAY,GAAI,QAAO;AAE/C,MAAI,QAAQ,SAAS,GAAG,EAAG,WAAU,UAAU;AAC/C,MAAI,UAAU,UAAU,SAAS,EAAE,KAAK,KAAK,CAAC,EAAG,QAAO;AAExD,MAAI,CAAC,QAAQ,SAAS,GAAG,KAAK,UAAU,UAAU,UAAU,OAAO,EAAE,KAAK,KAAK,CAAC,EAAG,QAAO;AAC1F,SAAO;AACT;AAMA,eAAsB,sBAAsB,YAA4C;AACtF,QAAM,UAAUA,MAAK,KAAK,YAAY,cAAc;AACpD,MAAI,CAAE,MAAM,OAAO,OAAO,EAAI,QAAO;AACrC,MAAI;AACF,UAAM,MAAM,MAAM,SAA4B,OAAO;AACrD,QAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,QAAI,OAAO,IAAI,WAAW,SAAU,QAAO,IAAI;AAC/C,QAAI,OAAO,IAAI,WAAW,YAAY,OAAO,IAAI,OAAO,SAAS,SAAU,QAAO,IAAI,OAAO;AAC7F,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,eAAsB,oBACpB,YACA,UACA,YAC6B;AAC7B,MAAI,cAAc,aAAa,QAAW;AACxC,UAAM,QAAQ,WAAW,YAAY,QAAQ;AAC7C,QAAI,MAAO,QAAO;AAAA,EACpB;AACA,QAAM,SAAS,MAAM,sBAAsB,UAAU;AACrD,SAAO,UAAU;AACnB;;;AHjFA,IAAM,qBAAqB;AAM3B,SAAS,iBAAyB;AAChC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,OAAO,SAAS,KAAK,EAAE;AACjC,SAAO,OAAO,SAAS,CAAC,KAAK,KAAK,IAAI,IAAI;AAC5C;AAEA,SAAS,eAAe,KAAuC;AAC7D,QAAM,KAAK,IAAI;AACf,MAAI,CAAC,GAAI,QAAO;AAChB,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO,GAAG,SAAS,IAAI,KAAK;AACnD,MAAI,MAAM,QAAQ,GAAG,QAAQ,EAAG,QAAO,GAAG,SAAS,SAAS,IAAI,GAAG,WAAW;AAC9E,SAAO;AACT;AAEA,eAAe,kBAAkB,KAA+B;AAC9D,SACG,MAAM,OAAOE,MAAK,KAAK,KAAK,gBAAgB,CAAC,KAC7C,MAAM,OAAOA,MAAK,KAAK,KAAK,kBAAkB,CAAC,KAC/C,MAAM,OAAOA,MAAK,KAAK,KAAK,UAAU,CAAC;AAE5C;AAEA,eAAe,cAAc,UAA0C;AACrE,QAAM,gBAAgBA,MAAK,KAAK,UAAU,YAAY;AACtD,MAAI,CAAE,MAAM,OAAO,aAAa,EAAI,QAAO;AAC3C,QAAM,MAAM,MAAMC,IAAG,SAAS,eAAe,MAAM;AACnD,SAAO,OAAO,EAAE,IAAI,GAAG;AACzB;AAOA,eAAe,SACb,OACA,UACA,SACA,OACe;AACf,iBAAe,QAAQ,SAAiB,OAA8B;AACpE,QAAI,QAAQ,QAAQ,SAAU;AAC9B,UAAM,UAAU,MAAMA,IAAG,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AACjF,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAM,QAAQD,MAAK,KAAK,SAAS,MAAM,IAAI;AAC3C,UAAI,QAAQ,IAAI;AACd,cAAM,MAAMA,MAAK,SAAS,UAAU,KAAK,EAAE,MAAMA,MAAK,GAAG,EAAE,KAAK,GAAG;AAInE,YAAI,OAAO,QAAQ,GAAG,QAAQ,MAAM,GAAG,EAAG;AAAA,MAC5C;AAMA,UAAI,MAAM,gBAAgB,KAAK,EAAG;AAClC,YAAM,MAAM,KAAK;AACjB,YAAM,QAAQ,OAAO,QAAQ,CAAC;AAAA,IAChC;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,CAAC;AACxB;AAEA,eAAe,qBACb,UACA,OACmB;AACnB,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,YAAY,eAAe;AAEjC,aAAW,OAAO,OAAO;AACvB,UAAM,UAAU,IAAI,QAAQ,SAAS,EAAE;AAEvC,QAAI,CAAC,QAAQ,SAAS,GAAG,GAAG;AAC1B,YAAM,YAAYA,MAAK,KAAK,UAAU,OAAO;AAC7C,UAAI,MAAM,OAAOA,MAAK,KAAK,WAAW,cAAc,CAAC,EAAG,OAAM,IAAI,SAAS;AAC3E;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,UAAM,iBAA2B,CAAC;AAClC,eAAW,OAAO,UAAU;AAC1B,UAAI,IAAI,SAAS,GAAG,EAAG;AACvB,qBAAe,KAAK,GAAG;AAAA,IACzB;AACA,UAAM,QAAQA,MAAK,KAAK,UAAU,GAAG,cAAc;AACnD,QAAI,CAAE,MAAM,OAAO,KAAK,EAAI;AAE5B,UAAM,gBAAgB,QAAQ,SAAS,IAAI;AAC3C,UAAM,YAAY,gBACd,YACA,KAAK,IAAI,GAAG,SAAS,SAAS,eAAe,SAAS,CAAC;AAE3D,UAAM,SAAS,OAAO,UAAU,EAAE,UAAU,WAAW,IAAI,KAAK,GAAG,OAAO,QAAQ;AAChF,YAAM,MAAMA,MAAK,SAAS,UAAU,GAAG,EAAE,MAAMA,MAAK,GAAG,EAAE,KAAK,GAAG;AACjE,UAAIE,WAAU,KAAK,OAAO,KAAM,MAAM,OAAOF,MAAK,KAAK,KAAK,cAAc,CAAC,GAAI;AAC7E,cAAM,IAAI,GAAG;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,CAAC,GAAG,KAAK;AAClB;AAQA,SAAS,kBAAkB,KAAsC;AAC/D,QAAM,OAAO,EAAE,GAAI,IAAI,gBAAgB,CAAC,GAAI,GAAI,IAAI,mBAAmB,CAAC,EAAG;AAC3E,MAAI,KAAK,MAAM,MAAM,OAAW,QAAO;AACvC,MAAI,KAAK,OAAO,MAAM,OAAW,QAAO;AACxC,aAAW,KAAK,OAAO,KAAK,IAAI,GAAG;AACjC,QAAI,EAAE,WAAW,aAAa,EAAG,QAAO;AAAA,EAC1C;AACA,MAAI,KAAK,eAAe,MAAM,OAAW,QAAO;AAChD,MAAI,KAAK,MAAM,MAAM,OAAW,QAAO;AACvC,MAAI,KAAK,OAAO,MAAM,OAAW,QAAO;AACxC,SAAO;AACT;AAOA,eAAe,wBACb,KACA,KACsC;AACtC,QAAM,OAAO,EAAE,GAAI,IAAI,gBAAgB,CAAC,GAAI,GAAI,IAAI,mBAAmB,CAAC,EAAG;AAC3E,MAAI,KAAK,YAAY,MAAM,OAAW,QAAO;AAC7C,QAAM,UAAU,MAAMC,IAAG,QAAQ,GAAG,EAAE,MAAM,MAAM,CAAC,CAAa;AAChE,MAAI,QAAQ,KAAK,CAAC,SAAS,0BAA0B,KAAK,IAAI,CAAC,EAAG,QAAO;AACzE,SAAO;AACT;AAEA,eAAe,oBACb,UACA,KACmC;AACnC,QAAM,UAAUD,MAAK,KAAK,KAAK,cAAc;AAC7C,MAAI,CAAE,MAAM,OAAO,OAAO,EAAI,QAAO;AACrC,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAsB,OAAO;AAAA,EAC3C,SAAS,KAAK;AACZ,0BAAsB,YAAYA,MAAK,SAAS,UAAU,OAAO,GAAG,GAAG;AACvE,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAI,KAAM,QAAO;AACtB,QAAM,YAAY,kBAAkB,GAAG;AACvC,QAAM,WAAW,MAAM,wBAAwB,KAAK,GAAG;AACvD,QAAM,OAAoB;AAAA,IACxB,IAAIG,WAAU,IAAI,IAAI;AAAA,IACtB,MAAMC,UAAS;AAAA,IACf,MAAM,IAAI;AAAA,IACV;AAAA,IACA,SAAS,IAAI;AAAA,IACb,cAAc,IAAI,gBAAgB,CAAC;AAAA,IACnC,UAAUJ,MAAK,SAAS,UAAU,GAAG;AAAA,IACrC,GAAI,IAAI,SAAS,OAAO,EAAE,YAAY,IAAI,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC5D,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACnC;AACA,SAAO,EAAE,KAAK,KAAK,KAAK;AAC1B;AAEA,eAAe,kBACb,UACA,KACmC;AACnC,QAAM,KAAK,MAAM,sBAAsB,GAAG;AAC1C,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,MAAM,gBAAgB,EAAE;AAC9B,QAAM,OAAoB;AAAA,IACxB,IAAIG,WAAU,GAAG,IAAI;AAAA,IACrB,MAAMC,UAAS;AAAA,IACf,MAAM,GAAG;AAAA,IACT,UAAU;AAAA,IACV,SAAS,GAAG;AAAA,IACZ,cAAc,GAAG;AAAA,IACjB,UAAUJ,MAAK,SAAS,UAAU,GAAG;AAAA,EACvC;AACA,SAAO,EAAE,KAAK,KAAK,KAAK;AAC1B;AAaA,eAAsB,iBAAiB,UAAgD;AACrF,QAAM,cAAcA,MAAK,KAAK,UAAU,cAAc;AACtD,MAAI,UAAkC;AACtC,MAAI,MAAM,OAAO,WAAW,GAAG;AAC7B,QAAI;AACF,gBAAU,MAAM,SAA0B,WAAW;AAAA,IACvD,SAAS,KAAK;AACZ;AAAA,QACE;AAAA,QACAA,MAAK,SAAS,UAAU,WAAW;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,UAAU,eAAe,OAAO,IAAI;AAEpD,QAAM,gBAA0B,CAAC;AACjC,MAAI,SAAS;AACX,kBAAc,KAAK,GAAI,MAAM,qBAAqB,UAAU,OAAO,CAAE;AAAA,EACvE,OAAO;AACL,QAAI,WAAW,QAAQ,MAAM;AAC3B,oBAAc,KAAK,QAAQ;AAAA,IAC7B,WAAW,MAAM,kBAAkB,QAAQ,GAAG;AAK5C,oBAAc,KAAK,QAAQ;AAAA,IAC7B;AACA,UAAM,KAAK,MAAM,cAAc,QAAQ;AACvC,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,EAAE,UAAU,eAAe,GAAG,GAAG;AAAA,MACjC,OAAO,QAAQ;AACb,YAAI,MAAM,OAAOA,MAAK,KAAK,KAAK,cAAc,CAAC,GAAG;AAChD,wBAAc,KAAK,GAAG;AAAA,QACxB,WAAW,MAAM,kBAAkB,GAAG,GAAG;AACvC,wBAAc,KAAK,GAAG;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,gBAAc,KAAK;AAEnB,QAAM,OAAO,oBAAI,IAAoB;AACrC,QAAM,MAA2B,CAAC;AAClC,aAAW,OAAO,eAAe;AAC/B,UAAM,UACH,MAAM,oBAAoB,UAAU,GAAG,KACvC,MAAM,kBAAkB,UAAU,GAAG;AACxC,QAAI,CAAC,QAAS;AAEd,UAAM,cAAc,KAAK,IAAI,QAAQ,KAAK,IAAI;AAC9C,QAAI,gBAAgB,QAAW;AAC7B,YAAM,IAAIA,MAAK,SAAS,UAAU,WAAW,KAAK;AAClD,YAAM,IAAIA,MAAK,SAAS,UAAU,GAAG,KAAK;AAC1C,cAAQ;AAAA,QACN,kCAAkC,QAAQ,KAAK,IAAI,oBAAe,CAAC,cAAc,CAAC;AAAA,MACpF;AACA;AAAA,IACF;AACA,SAAK,IAAI,QAAQ,KAAK,MAAM,GAAG;AAC/B,QAAI,KAAK,OAAO;AAAA,EAClB;AAKA,QAAM,aAAa,MAAM,eAAe,QAAQ;AAChD,aAAW,WAAW,KAAK;AACzB,UAAM,QAAQ,MAAM,oBAAoB,YAAY,QAAQ,KAAK,UAAU,QAAQ,GAAG;AACtF,QAAI,UAAU,OAAW,SAAQ,KAAK,QAAQ;AAAA,EAChD;AAEA,SAAO;AACT;AAEO,SAAS,gBAAgB,OAAkB,UAAuC;AACvF,MAAI,aAAa;AACjB,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,EAAE,GAAG;AACnC,YAAM,QAAQ,QAAQ,KAAK,IAAI,EAAE,GAAG,QAAQ,MAAM,eAAe,SAAS,CAAC;AAC3E;AACA;AAAA,IACF;AAIA,UAAM,WAAW,MAAM,kBAAkB,QAAQ,KAAK,EAAE;AACxD,UAAM,sBACJ,SAAS,kBAAkB,SAAS,WAAW;AACjD,UAAM,sBAAsB,QAAQ,KAAK,IAAI;AAAA,MAC3C,GAAG;AAAA,MACH,GAAG,QAAQ;AAAA,MACX,eAAe;AAAA,IACjB,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AIzUA,OAAOK,WAAU;AACjB,SAAS,YAAYC,WAAU;AAC/B,SAAS,yBAAyB;AAElC,SAAS,YAAAC,iBAAgB;AAiDzB,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,WAAW,OAAkBC,YAAmB,YAAoC;AAC3F,MAAI,CAAC,MAAM,QAAQA,UAAS,EAAG;AAC/B,QAAM,OAAO,MAAM,kBAAkBA,UAAS;AAC9C,MAAI,KAAK,SAASC,UAAS,YAAa;AACxC,QAAM,MAAM,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC;AACtC,aAAW,KAAK,YAAY;AAC1B,QAAI,CAAC,EAAG;AACR,QAAI,MAAM,KAAK,KAAM;AACrB,QAAI,IAAI,CAAC;AAAA,EACX;AACA,MAAI,IAAI,SAAS,EAAG;AACpB,QAAM,UAAuB,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE;AACjE,QAAM,sBAAsBD,YAAW,OAAO;AAChD;AAEA,SAAS,oBAAoB,UAAoD;AAC/E,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,KAAK,UAAU;AACxB,QAAI,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE;AAC9B,QAAI,IAAIE,MAAK,SAAS,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE;AAAA,EACzC;AACA,SAAO;AACT;AAEA,eAAe,sBACb,OACA,UACA,cACe;AACf,MAAI,cAA6B;AACjC,aAAW,QAAQ,CAAC,sBAAsB,qBAAqB,GAAG;AAChE,UAAM,MAAMA,MAAK,KAAK,UAAU,IAAI;AACpC,QAAI,MAAM,OAAO,GAAG,GAAG;AACrB,oBAAc;AACd;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,YAAa;AAElB,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,SAAsB,WAAW;AAAA,EACnD,SAAS,KAAK;AACZ;AAAA,MACE;AAAA,MACAA,MAAK,SAAS,UAAU,WAAW;AAAA,MACnC;AAAA,IACF;AACA;AAAA,EACF;AACA,MAAI,CAAC,SAAS,SAAU;AAExB,aAAW,CAAC,aAAa,GAAG,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AACjE,UAAMF,aAAY,aAAa,IAAI,WAAW;AAC9C,QAAI,CAACA,WAAW;AAChB,UAAM,UAAU,oBAAI,IAAY,CAAC,WAAW,CAAC;AAC7C,QAAI,IAAI,eAAgB,SAAQ,IAAI,IAAI,cAAc;AACtD,QAAI,IAAI,SAAU,SAAQ,IAAI,IAAI,QAAQ;AAC1C,eAAW,OAAOA,YAAW,OAAO;AAAA,EACtC;AACF;AAEA,IAAM,aAAa,oBAAI,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,sBAAsB,SAA2B;AACxD,QAAM,MAAgB,CAAC;AAIvB,QAAM,YAAY;AAClB,aAAW,OAAO,QAAQ,MAAM,IAAI,GAAG;AACrC,UAAM,IAAI,UAAU,KAAK,GAAG;AAC5B,QAAI,CAAC,EAAG;AACR,UAAM,OAAO,EAAE,CAAC;AAChB,UAAM,YAAY;AAClB,QAAI;AACJ,YAAQ,OAAO,UAAU,KAAK,IAAI,OAAO,MAAM;AAC7C,YAAM,MAAM,KAAK,CAAC,EAAG,YAAY;AACjC,UAAI,CAAC,WAAW,IAAI,GAAG,EAAG;AAC1B,YAAM,QAAQ,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK;AAC/C,UAAI,MAAO,KAAI,KAAK,KAAK;AAAA,IAC3B;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,yBACb,OACA,UACe;AACf,aAAW,WAAW,UAAU;AAC9B,UAAM,iBAAiBE,MAAK,KAAK,QAAQ,KAAK,YAAY;AAC1D,QAAI,CAAE,MAAM,OAAO,cAAc,EAAI;AACrC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMC,IAAG,SAAS,gBAAgB,MAAM;AAAA,IACpD,SAAS,KAAK;AACZ,4BAAsB,sBAAsB,gBAAgB,GAAG;AAC/D;AAAA,IACF;AACA,UAAM,UAAU,sBAAsB,OAAO;AAC7C,QAAI,QAAQ,SAAS,EAAG,YAAW,OAAO,QAAQ,KAAK,IAAI,OAAO;AAAA,EACpE;AACF;AAEA,eAAe,cAAc,OAAe,QAAQ,GAAG,MAAM,GAAsB;AACjF,MAAI,QAAQ,IAAK,QAAO,CAAC;AACzB,QAAM,MAAgB,CAAC;AACvB,QAAM,UAAU,MAAMA,IAAG,QAAQ,OAAO,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC/E,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAM,QAAQD,MAAK,KAAK,OAAO,MAAM,IAAI;AACzC,UAAI,MAAM,gBAAgB,KAAK,EAAG;AAClC,UAAI,KAAK,GAAI,MAAM,cAAc,OAAO,QAAQ,GAAG,GAAG,CAAE;AAAA,IAC1D,WAAW,MAAM,OAAO,KAAK,uBAAuB,IAAIA,MAAK,QAAQ,MAAM,IAAI,CAAC,GAAG;AACjF,UAAI,KAAKA,MAAK,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAc,WAAyC;AAC3E,QAAM,KAAK,aAAa;AACxB,SAAO;AAAA,IACL;AAAA,IACA,GAAG,IAAI,IAAI,EAAE;AAAA,IACb,GAAG,IAAI,IAAI,EAAE;AAAA,IACb,GAAG,IAAI,IAAI,EAAE;AAAA,EACf;AACF;AAEA,SAAS,iBACP,KACA,QACe;AAIf,QAAM,WAAW,IAAI,MAAM;AAC3B,QAAM,cAAc,UAAU,OAAO,UAAU,aAAa;AAC5D,MAAI,eAAe,OAAO,IAAI,WAAW,EAAG,QAAO,OAAO,IAAI,WAAW;AAEzE,QAAM,WAAW,IAAI,UAAU,QAAQ;AACvC,MAAI,YAAY,OAAO,IAAI,QAAQ,EAAG,QAAO,OAAO,IAAI,QAAQ;AAEhE,QAAM,WAAW,IAAI,UAAU;AAC/B,MAAI,YAAY,OAAO,IAAI,QAAQ,EAAG,QAAO,OAAO,IAAI,QAAQ;AAEhE,SAAO;AACT;AAEA,eAAe,kBACb,OACA,UACA,cACe;AACf,QAAM,QAAQ,MAAM,cAAc,QAAQ;AAC1C,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,MAAMC,IAAG,SAAS,MAAM,MAAM;AAC9C,QAAI;AACJ,QAAI;AACF,aAAO,kBAAkB,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAW;AAAA,IACnE,QAAQ;AACN;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,UAAU,KAAM;AACvC,UAAI,CAAC,yBAAyB,IAAI,IAAI,IAAI,EAAG;AAC7C,YAAM,SAAS,iBAAiB,KAAK,YAAY;AACjD,UAAI,CAAC,OAAQ;AACb,iBAAW,OAAO,QAAQ,aAAa,IAAI,SAAS,MAAM,IAAI,SAAS,SAAS,CAAC;AAAA,IACnF;AAAA,EACF;AACF;AAEA,eAAsB,kBACpB,OACA,UACA,UACe;AACf,QAAM,SAAS,oBAAoB,QAAQ;AAC3C,QAAM,sBAAsB,OAAO,UAAU,MAAM;AACnD,QAAM,yBAAyB,OAAO,QAAQ;AAC9C,QAAM,kBAAkB,OAAO,UAAU,MAAM;AACjD;;;AC5PA,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AAEjB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,EACA,mBAAAC;AAAA,EACA,UAAAC;AAAA,OACK;AAoBA,SAAS,sBAAsB,UAAiD;AACrF,QAAM,aAAa,oBAAI,IAAY;AACnC,QAAM,eAAe,oBAAI,IAAoB;AAC7C,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAOC,OAAK,SAAS,QAAQ,GAAG;AACtC,eAAW,IAAI,IAAI;AACnB,eAAW,IAAI,QAAQ,IAAI,IAAI;AAC/B,iBAAa,IAAI,MAAM,QAAQ,KAAK,EAAE;AACtC,iBAAa,IAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK,EAAE;AAAA,EACpD;AACA,SAAO,EAAE,YAAY,aAAa;AACpC;AAsBA,eAAsB,gBAAgB,KAAgC;AACpE,QAAM,MAAgB,CAAC;AACvB,iBAAeC,MAAK,SAAgC;AAClD,UAAM,UAAU,MAAMC,KAAG,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AACjF,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAOF,OAAK,KAAK,SAAS,MAAM,IAAI;AAC1C,UAAI,MAAM,YAAY,GAAG;AACvB,YAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAI,MAAM,gBAAgB,IAAI,EAAG;AACjC,cAAMC,MAAK,IAAI;AAAA,MACjB,WACE,MAAM,OAAO,KACb,wBAAwB,IAAID,OAAK,QAAQ,MAAM,IAAI,CAAC;AAAA;AAAA,MAGpD,CAAC,yBAAyB,MAAM,IAAI,GACpC;AACA,YAAI,KAAK,IAAI;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACA,QAAMC,MAAK,GAAG;AACd,SAAO;AACT;AAEA,eAAsB,gBAAgB,KAAoC;AACxE,QAAM,QAAQ,MAAM,gBAAgB,GAAG;AACvC,QAAM,MAAoB,CAAC;AAC3B,aAAW,KAAK,OAAO;AACrB,QAAI;AACF,YAAM,UAAU,MAAMC,KAAG,SAAS,GAAG,MAAM;AAC3C,UAAI,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;AAAA,IAC/B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,OAAO,MAAc,QAAwB;AAC3D,QAAM,MAAM,KAAK,QAAQ,MAAM;AAC/B,MAAI,MAAM,EAAG,QAAO;AACpB,SAAO,KAAK,MAAM,GAAG,GAAG,EAAE,MAAM,IAAI,EAAE;AACxC;AAEO,SAAS,QAAQ,MAAc,MAAsB;AAC1D,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,UAAQ,MAAM,OAAO,CAAC,KAAK,IAAI,KAAK;AACtC;AAIO,SAASC,SAAQ,GAAmB;AACzC,SAAO,EAAE,MAAM,IAAI,EAAE,KAAK,GAAG;AAC/B;AAIO,SAAS,gBAAgB,SAAqC;AACnE,UAAQH,OAAK,QAAQ,OAAO,EAAE,YAAY,GAAG;AAAA,IAC3C,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AASO,SAAS,eACd,OACA,aACA,eACA,SACgE;AAChE,MAAI,aAAa;AACjB,MAAI,aAAa;AACjB,QAAM,aAAaI,QAAO,aAAa,OAAO;AAC9C,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,WAAW,gBAAgB,OAAO;AACxC,UAAM,OAAiB;AAAA,MACrB,IAAI;AAAA,MACJ,MAAMC,UAAS;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/B,eAAe;AAAA,IACjB;AACA,UAAM,QAAQ,YAAY,IAAI;AAC9B;AAAA,EACF;AACA,QAAM,aAAaC,iBAAgB,eAAe,YAAYC,UAAS,QAAQ;AAC/E,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,OAAkB;AAAA,MACtB,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,MAAMA,UAAS;AAAA,MACf,YAAYC,YAAW;AAAA,MACvB,YAAY,uBAAuB,YAAY;AAAA,MAC/C,UAAU,EAAE,MAAM,QAAQ;AAAA,IAC5B;AACA,UAAM,eAAe,YAAY,eAAe,YAAY,IAAI;AAChE;AAAA,EACF;AACA,SAAO,EAAE,YAAY,YAAY,WAAW;AAC9C;;;ACtLA,OAAOC,YAAU;AAMjB,eAAsB,SACpB,OACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,YAAY,MAAM,gBAAgB,QAAQ,GAAG;AACnD,eAAW,YAAY,WAAW;AAChC,YAAM,UAAUC,SAAQD,OAAK,SAAS,QAAQ,KAAK,QAAQ,CAAC;AAC5D,YAAM,EAAE,YAAY,GAAG,YAAY,EAAE,IAAI;AAAA,QACvC;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb;AAAA,MACF;AACA,oBAAc;AACd,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AChCA,OAAOE,YAAU;AACjB,SAAS,YAAYC,YAAU;AAC/B,OAAO,YAAY;AACnB,OAAO,gBAAgB;AACvB,OAAO,YAAY;AAEnB;AAAA,EACE,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,mBAAAC;AAAA,EACA,UAAAC;AAAA,OACK;AAeP,IAAM,cAAc;AAEpB,SAAS,YAAY,QAAgB,QAA6B;AAChE,SAAO,OAAO;AAAA,IAAM,CAAC,UACnB,SAAS,OAAO,SAAS,KAAK,OAAO,MAAM,OAAO,QAAQ,WAAW;AAAA,EACvE;AACF;AAEA,SAAS,eAAuB;AAC9B,QAAM,IAAI,IAAI,OAAO;AACrB,IAAE,YAAY,UAAU;AACxB,SAAO;AACT;AAEA,SAAS,eAAuB;AAC9B,QAAM,IAAI,IAAI,OAAO;AACrB,IAAE,YAAY,MAAM;AACpB,SAAO;AACT;AAQA,SAAS,kBAAkB,MAAwC;AACjE,WAAS,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK;AACxC,UAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,QAAI,OAAO,SAAS,kBAAmB,QAAO,MAAM;AAAA,EACtD;AACA,QAAM,MAAM,KAAK;AACjB,MAAI,IAAI,UAAU,EAAG,QAAO,IAAI,MAAM,GAAG,EAAE;AAC3C,SAAO,IAAI,WAAW,IAAI,OAAO;AACnC;AAEA,SAAS,YAAY,MAAsB;AACzC,QAAM,UAAU,KAAK,MAAM,IAAI,EAAE,CAAC,KAAK;AACvC,SAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,GAAG,GAAG,IAAI;AACxD;AAaA,SAAS,iBAAiB,MAAyB,KAAwB;AACzE,MAAI,KAAK,SAAS,oBAAoB;AACpC,UAAM,SAAS,KAAK,kBAAkB,QAAQ;AAC9C,QAAI,QAAQ;AACV,YAAM,YAAY,kBAAkB,MAAM;AAC1C,UAAI,WAAW;AACb,YAAI,KAAK,EAAE,WAAW,MAAM,KAAK,cAAc,MAAM,GAAG,SAAS,YAAY,KAAK,IAAI,EAAE,CAAC;AAAA,MAC3F;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,KAAK,SAAS,mBAAmB;AACnC,UAAM,KAAK,KAAK,kBAAkB,UAAU;AAC5C,QAAI,IAAI,SAAS,gBAAgB,GAAG,SAAS,WAAW;AACtD,YAAM,OAAO,KAAK,kBAAkB,WAAW;AAC/C,YAAM,WAAW,MAAM,WAAW,CAAC;AACnC,UAAI,UAAU,SAAS,UAAU;AAC/B,cAAM,YAAY,kBAAkB,QAAQ;AAC5C,YAAI,WAAW;AACb,cAAI,KAAK,EAAE,WAAW,MAAM,KAAK,cAAc,MAAM,GAAG,SAAS,YAAY,KAAK,IAAI,EAAE,CAAC;AAAA,QAC3F;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,kBAAiB,OAAO,GAAG;AAAA,EACxC;AACF;AAiBA,SAAS,qBAAqB,MAAyB,KAAqB;AAC1E,MAAI,KAAK,SAAS,kBAAkB;AAClC,UAAM,WAAW,KAAK,kBAAkB,MAAM;AAC9C,QAAI,SAAU,KAAI,KAAK,SAAS,IAAI;AACpC;AAAA,EACF;AACA,MAAI,KAAK,SAAS,eAAe;AAC/B,QAAI,KAAK,KAAK,IAAI;AAClB;AAAA,EACF;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,sBAAqB,OAAO,GAAG;AAAA,EAC5C;AACF;AAQA,SAAS,iBAAiB,MAAyB,KAA0B;AAC3E,MAAI,KAAK,SAAS,yBAAyB;AACzC,QAAI,QAAQ;AACZ,QAAI,aAAa;AACjB,UAAM,QAAkB,CAAC;AACzB,QAAI,WAAW;AACf,QAAI,aAAa;AAEjB,aAAS,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK;AACxC,YAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAI,CAAC,MAAO;AACZ,UAAI,CAAC,UAAU;AACb,YAAI,MAAM,SAAS,OAAQ,YAAW;AACtC;AAAA,MACF;AACA,UAAI,CAAC,YAAY;AACf,YAAI,MAAM,SAAS,UAAU;AAC3B,uBAAa;AACb;AAAA,QACF;AAEA,YAAI,MAAM,SAAS,mBAAmB;AACpC,mBAAS,IAAI,GAAG,IAAI,MAAM,YAAY,KAAK;AACzC,kBAAM,KAAK,MAAM,MAAM,CAAC;AACxB,gBAAI,CAAC,GAAI;AAMT,gBAAI,GAAG,SAAS,iBAAiB;AAC/B,uBAAS,IAAI,GAAG,IAAI,GAAG,YAAY,KAAK;AACtC,oBAAI,GAAG,MAAM,CAAC,GAAG,SAAS,IAAK;AAAA,cACjC;AAAA,YACF,WAAW,GAAG,SAAS,cAAe,cAAa,GAAG;AAAA,UACxD;AAAA,QACF,WAAW,MAAM,SAAS,eAAe;AACvC,uBAAa,MAAM;AAAA,QACrB;AACA;AAAA,MACF;AAEA,2BAAqB,OAAO,KAAK;AAAA,IACnC;AAEA,QAAI,QAAQ,KAAK,YAAY;AAC3B,UAAI,KAAK,EAAE,YAAY,OAAO,OAAO,MAAM,KAAK,cAAc,MAAM,GAAG,SAAS,YAAY,KAAK,IAAI,EAAE,CAAC;AAAA,IAC1G;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,kBAAiB,OAAO,GAAG;AAAA,EACxC;AACF;AAIA,eAAe,WAAW,GAA6B;AACrD,MAAI;AACF,UAAMC,KAAG,OAAO,CAAC;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,mBAAmB,WAAmB,YAA6B;AAC1E,QAAM,MAAMC,OAAK,SAAS,YAAY,SAAS;AAC/C,SAAO,QAAQ,MAAM,CAAC,IAAI,WAAW,IAAI,KAAK,CAACA,OAAK,WAAW,GAAG;AACpE;AAEA,IAAM,gBAAgB,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AACnE,IAAM,iBAAiB,cAAc,IAAI,CAAC,QAAQ,QAAQ,GAAG,EAAE;AAI/D,eAAe,uBACb,MACA,YACwB;AACxB,aAAW,OAAO,eAAe;AAC/B,UAAM,YAAY,OAAO;AACzB,QAAI,mBAAmB,WAAW,UAAU,KAAM,MAAM,WAAW,SAAS,GAAI;AAC9E,aAAOC,SAAQD,OAAK,SAAS,YAAY,SAAS,CAAC;AAAA,IACrD;AAAA,EACF;AACA,aAAW,aAAa,gBAAgB;AACtC,UAAM,YAAYA,OAAK,KAAK,MAAM,SAAS;AAC3C,QAAI,mBAAmB,WAAW,UAAU,KAAM,MAAM,WAAW,SAAS,GAAI;AAC9E,aAAOC,SAAQD,OAAK,SAAS,YAAY,SAAS,CAAC;AAAA,IACrD;AAAA,EACF;AACA,SAAO;AACT;AAWA,eAAe,iBAAiB,YAAkD;AAChF,QAAM,eAAeA,OAAK,KAAK,YAAY,eAAe;AAC1D,MAAI;AACJ,MAAI;AACF,UAAM,MAAMD,KAAG,SAAS,cAAc,MAAM;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAG7B,UAAM,QAAQ,OAAO,iBAAiB;AACtC,QAAI,CAAC,SAAS,OAAO,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AACtD,UAAM,UAAU,OAAO,iBAAiB;AACxC,WAAO,EAAE,OAAO,SAAS,UAAUC,OAAK,QAAQ,YAAY,OAAO,IAAI,WAAW;AAAA,EACpF,SAAS,KAAK;AACZ,0BAAsB,2BAA2B,cAAc,GAAG;AAClE,WAAO;AAAA,EACT;AACF;AAMA,eAAe,eACb,WACA,QACA,YACwB;AACxB,aAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,KAAK,GAAG;AAC7D,QAAI,SAAwB;AAC5B,QAAI,YAAY,WAAW;AACzB,eAAS;AAAA,IACX,WAAW,QAAQ,SAAS,IAAI,GAAG;AACjC,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,UAAI,UAAU,WAAW,MAAM,EAAG,UAAS,UAAU,MAAM,OAAO,MAAM;AAAA,IAC1E;AACA,QAAI,WAAW,KAAM;AAErB,eAAW,UAAU,SAAS;AAC5B,YAAM,aAAa,OAAO,SAAS,IAAI,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,OAAO,QAAQ,OAAO,EAAE;AACzF,YAAM,eAAeA,OAAK,QAAQ,OAAO,SAAS,YAAY,MAAM;AACpE,YAAM,MAAM,MAAM,uBAAuB,cAAc,UAAU;AACjE,UAAI,IAAK,QAAO;AAEhB,UAAI,mBAAmB,cAAc,UAAU,KAAM,MAAM,WAAW,YAAY,GAAI;AACpF,eAAOC,SAAQD,OAAK,SAAS,YAAY,YAAY,CAAC;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAKA,eAAe,gBACb,WACA,aACA,YACA,SACwB;AACxB,MAAI,CAAC,UAAW,QAAO;AAEvB,MAAI,UAAU,WAAW,IAAI,KAAK,UAAU,WAAW,KAAK,GAAG;AAC7D,UAAM,OAAOA,OAAK,QAAQ,aAAa,SAAS;AAChD,UAAM,MAAMA,OAAK,QAAQ,SAAS;AAElC,QAAI,KAAK;AAIP,UAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,cAAM,QAAQ,QAAQ,SAAS,SAAS;AACxC,cAAM,YAAY,KAAK,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI;AAC/C,YAAI,mBAAmB,WAAW,UAAU,KAAM,MAAM,WAAW,SAAS,GAAI;AAC9E,iBAAOC,SAAQD,OAAK,SAAS,YAAY,SAAS,CAAC;AAAA,QACrD;AAAA,MACF;AACA,UAAI,mBAAmB,MAAM,UAAU,KAAM,MAAM,WAAW,IAAI,GAAI;AACpE,eAAOC,SAAQD,OAAK,SAAS,YAAY,IAAI,CAAC;AAAA,MAChD;AACA,aAAO;AAAA,IACT;AAEA,WAAO,uBAAuB,MAAM,UAAU;AAAA,EAChD;AAIA,MAAI,QAAS,QAAO,eAAe,WAAW,SAAS,UAAU;AACjE,SAAO;AACT;AAcA,eAAe,gBACb,KACA,cACA,YACmB;AACnB,MAAI;AACJ,MAAI,IAAI,QAAQ,GAAG;AACjB,cAAUA,OAAK,QAAQ,YAAY;AACnC,aAAS,IAAI,GAAG,IAAI,IAAI,OAAO,IAAK,WAAUA,OAAK,QAAQ,OAAO;AAAA,EACpE,OAAO;AACL,cAAU;AAAA,EACZ;AAIA,QAAM,aAAa,IAAI,aACnBA,OAAK,KAAK,SAAS,IAAI,WAAW,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,IACtD;AAEJ,QAAM,WAAW,oBAAI,IAAY;AACjC,MAAI,iBAAiB,IAAI,MAAM,WAAW;AAE1C,aAAW,QAAQ,IAAI,OAAO;AAC5B,UAAM,gBAAgBA,OAAK,KAAK,YAAY,GAAG,IAAI,KAAK;AACxD,UAAM,iBAAiBA,OAAK,KAAK,YAAY,MAAM,aAAa;AAChE,QAAI,mBAAmB,eAAe,UAAU,KAAM,MAAM,WAAW,aAAa,GAAI;AACtF,eAAS,IAAIC,SAAQD,OAAK,SAAS,YAAY,aAAa,CAAC,CAAC;AAAA,IAChE,WAAW,mBAAmB,gBAAgB,UAAU,KAAM,MAAM,WAAW,cAAc,GAAI;AAC/F,eAAS,IAAIC,SAAQD,OAAK,SAAS,YAAY,cAAc,CAAC,CAAC;AAAA,IACjE,OAAO;AAEL,uBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,gBAAgB;AAGlB,UAAM,uBAAuB,IAAI,aAC7B,CAAC,GAAG,UAAU,OAAOA,OAAK,KAAK,YAAY,aAAa,CAAC,IACzD,CAACA,OAAK,KAAK,YAAY,aAAa,CAAC;AACzC,eAAW,aAAa,sBAAsB;AAC5C,UAAI,mBAAmB,WAAW,UAAU,KAAM,MAAM,WAAW,SAAS,GAAI;AAC9E,iBAAS,IAAIC,SAAQD,OAAK,SAAS,YAAY,SAAS,CAAC,CAAC;AAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,QAAQ;AACrB;AAIA,SAAS,eACP,OACA,aACA,gBACA,iBACA,iBACA,MACAE,UACQ;AACR,QAAM,iBAAiBC,QAAO,aAAa,eAAe;AAI1D,MAAI,CAAC,MAAM,QAAQ,cAAc,EAAG,QAAO;AAE3C,QAAM,SAASC,iBAAgB,gBAAgB,gBAAgBC,UAAS,OAAO;AAC/E,MAAI,MAAM,QAAQ,MAAM,EAAG,QAAO;AAElC,QAAM,OAAkB;AAAA,IACtB,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAMA,UAAS;AAAA,IACf,YAAYC,YAAW;AAAA,IACvB,YAAYC,wBAAuB,YAAY;AAAA,IAC/C,UAAU,EAAE,MAAM,iBAAiB,MAAM,SAAAL,SAAQ;AAAA,EACnD;AACA,QAAM,eAAe,QAAQ,gBAAgB,gBAAgB,IAAI;AACjE,SAAO;AACT;AAIA,eAAsB,WACpB,OACA,UACqD;AACrD,QAAM,WAAW,aAAa;AAC9B,QAAM,WAAW,aAAa;AAC9B,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAU,MAAM,iBAAiB,QAAQ,GAAG;AAClD,UAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG;AAE/C,eAAW,QAAQ,OAAO;AAGxB,UAAI,WAAW,KAAK,IAAI,EAAG;AAE3B,YAAM,UAAUD,SAAQD,OAAK,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC;AAC7D,YAAM,iBAAiBG,QAAO,QAAQ,IAAI,MAAM,OAAO;AACvD,YAAM,WAAWH,OAAK,QAAQ,KAAK,IAAI,MAAM;AAE7C,UAAI,UAAU;AACZ,YAAI,YAA2B,CAAC;AAChC,YAAI;AACF,gBAAM,OAAO,YAAY,UAAU,KAAK,OAAO;AAC/C,2BAAiB,KAAK,UAAU,SAAS;AAAA,QAC3C,SAAS,KAAK;AACZ,gCAAsB,qBAAqB,KAAK,MAAM,GAAG;AACzD;AAAA,QACF;AACA,mBAAW,OAAO,WAAW;AAC3B,gBAAM,gBAAgB,MAAM,gBAAgB,KAAK,KAAK,MAAM,QAAQ,GAAG;AACvE,qBAAW,YAAY,eAAe;AACpC,0BAAc;AAAA,cACZ;AAAA,cACA,QAAQ,IAAI;AAAA,cACZ;AAAA,cACA;AAAA,cACA;AAAA,cACA,IAAI;AAAA,cACJ,IAAI;AAAA,YACN;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAEA,UAAI,YAAyB,CAAC;AAC9B,UAAI;AACF,cAAM,OAAO,YAAY,UAAU,KAAK,OAAO;AAC/C,yBAAiB,KAAK,UAAU,SAAS;AAAA,MAC3C,SAAS,KAAK;AACZ,8BAAsB,qBAAqB,KAAK,MAAM,GAAG;AACzD;AAAA,MACF;AACA,iBAAW,OAAO,WAAW;AAC3B,cAAM,WAAW,MAAM,gBAAgB,IAAI,WAAWA,OAAK,QAAQ,KAAK,IAAI,GAAG,QAAQ,KAAK,OAAO;AACnG,YAAI,CAAC,SAAU;AACf,sBAAc;AAAA,UACZ;AAAA,UACA,QAAQ,IAAI;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,GAAG,WAAW;AACrC;;;AC3gBA,OAAOQ,YAAU;AASjB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,OACK;;;AChBP,OAAOC,YAAU;AAejB,eAAsB,MAAM,YAAyC;AACnE,QAAM,WAAWC,OAAK,KAAK,YAAY,gBAAgB;AACvD,MAAI,CAAE,MAAM,OAAO,QAAQ,EAAI,QAAO,CAAC;AACvC,QAAM,MAAM,MAAM,SAAuB,QAAQ;AACjD,SAAO;AAAA,IACL;AAAA,MACE,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,eAAe,IAAI,kBAAkB,SAAY,OAAO,IAAI,aAAa,IAAI;AAAA,MAC7E,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAEO,IAAM,qBAAqB,EAAE,MAAM,kBAAkB,MAAM;;;AC/BlE,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;;;ACDjB,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AAkBV,SAAS,eAAe,QAA+B;AAC5D,QAAM,IAAI,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3C,UAAQ,GAAG;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAMO,SAAS,sBAAsB,KAA4C;AAChF,QAAM,IAAI,IAAI;AAAA,IACZ;AAAA,EACF;AACA,MAAI,CAAC,KAAK,CAAC,EAAE,OAAQ,QAAO;AAC5B,QAAM,SAAS,eAAe,EAAE,OAAO,MAAO;AAC9C,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO;AAAA,IACL,MAAM,EAAE,OAAO;AAAA,IACf,MAAM,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,IAAI,IAAI;AAAA,IAC9C,UAAU,EAAE,OAAO,MAAM;AAAA,IACzB;AAAA,IACA,eAAe;AAAA,EACjB;AACF;AAEA,eAAsB,aAAa,UAA0C;AAC3E,MAAI;AACF,WAAO,MAAMD,KAAG,SAAS,UAAU,MAAM;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,UACpB,YACA,YACwB;AACxB,aAAW,OAAO,YAAY;AAC5B,UAAM,MAAMC,OAAK,KAAK,YAAY,GAAG;AACrC,UAAM,UAAU,MAAM,aAAa,GAAG;AACtC,QAAI,YAAY,KAAM,QAAO;AAAA,EAC/B;AACA,SAAO;AACT;AAKO,SAAS,gBACd,OACkD;AAClD,QAAM,QAAQ,MAAM,YAAY;AAChC,QAAM,QAAQ,MAAM,YAAY,GAAG;AACnC,QAAM,OAAO,SAAS,IAAI,MAAM,MAAM,GAAG,KAAK,IAAI;AAClD,QAAM,MAAM,SAAS,IAAI,MAAM,MAAM,QAAQ,CAAC,IAAI;AAClD,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AACtC,MAAI,SAAwB;AAC5B,MAAI,KAAK,WAAW,UAAU,EAAG,UAAS;AAAA,WACjC,KAAK,WAAW,OAAO,KAAK,KAAK,WAAW,SAAS,EAAG,UAAS;AAAA,WACjE,KAAK,WAAW,OAAO,EAAG,UAAS;AAAA,WACnC,KAAK,WAAW,OAAO,EAAG,UAAS;AAAA,WACnC,KAAK,WAAW,QAAQ,EAAG,UAAS;AAC7C,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,eAAe,IAAI,MAAM,sBAAsB;AACrD,SAAO;AAAA,IACL;AAAA,IACA,eAAe,eAAe,aAAa,CAAC,IAAK;AAAA,EACnD;AACF;;;ADpGA,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,SAAS,gBAAgB,MAAqD;AAC5E,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG,QAAO;AAChD,QAAM,KAAK,QAAQ,QAAQ,GAAG;AAC9B,MAAI,KAAK,EAAG,QAAO;AACnB,QAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AACtC,MAAI,QAAQ,QAAQ,MAAM,KAAK,CAAC,EAAE,KAAK;AACvC,MACG,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC5C;AACA,YAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,EAC3B;AACA,SAAO,EAAE,KAAK,MAAM;AACtB;AAEA,eAAsBC,OAAM,YAAyC;AACnE,QAAM,UAAU,MAAMC,KAAG,QAAQ,YAAY,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AACpF,QAAM,UAAsB,CAAC;AAC7B,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,OAAO,EAAG;AACrB,UAAM,QAAQ,aAAa,MAAM,IAAI;AACrC,QAAI,CAAC,MAAM,SAAS,MAAM,aAAa,MAAO;AAE9C,UAAM,WAAWC,OAAK,KAAK,YAAY,MAAM,IAAI;AACjD,UAAM,UAAU,MAAMD,KAAG,SAAS,UAAU,MAAM;AAClD,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,YAAM,SAAS,gBAAgB,IAAI;AACnC,UAAI,CAAC,OAAQ;AACb,UAAI,CAAC,gBAAgB,IAAI,OAAO,IAAI,YAAY,CAAC,EAAG;AACpD,YAAM,SAAS,sBAAsB,OAAO,KAAK;AACjD,UAAI,CAAC,OAAQ;AACb,YAAM,MAAM,GAAG,OAAO,MAAM,MAAM,OAAO,IAAI,IAAI,OAAO,QAAQ,EAAE,IAAI,OAAO,QAAQ;AACrF,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,cAAQ,KAAK,EAAE,GAAG,QAAQ,YAAY,SAAS,CAAC;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,eAAe,EAAE,MAAM,QAAQ,OAAAD,OAAM;;;AE9DlD,OAAOG,YAAU;AAajB,eAAsBC,OAAM,YAAyC;AACnE,QAAM,aAAaC,OAAK,KAAK,YAAY,UAAU,eAAe;AAClE,QAAM,UAAU,MAAM,aAAa,UAAU;AAC7C,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,QAAQ,QAAQ,MAAM,iCAAiC;AAC7D,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,OAAO,MAAM,CAAC,KAAK;AAEzB,QAAM,gBAAgB,KAAK,MAAM,0BAA0B;AAC3D,MAAI,CAAC,cAAe,QAAO,CAAC;AAC5B,QAAM,SAAS,eAAe,cAAc,CAAC,CAAE;AAC/C,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,WAAW,KAAK,MAAM,qBAAqB;AACjD,MAAI,UAAU;AACZ,UAAM,SAAS,sBAAsB,SAAS,CAAC,CAAE;AACjD,QAAI,OAAQ,QAAO,CAAC,EAAE,GAAG,QAAQ,YAAY,WAAW,CAAC;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL;AAAA,MACE,MAAM,GAAG,MAAM;AAAA,MACf,UAAU;AAAA,MACV;AAAA,MACA,eAAe;AAAA,MACf,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAEO,IAAM,eAAe,EAAE,MAAM,UAAU,OAAAD,OAAM;;;AC1CpD,IAAM,oBAA4C;AAAA,EAChD,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,iBAAiB;AACnB;AAKA,eAAsBE,OAAM,YAAyC;AACnE,QAAM,WAAW,MAAM,UAAU,YAAY;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,SAAU,QAAO,CAAC;AACvB,QAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,eAAe,QAAQ,MAAM,mCAAmC;AACtE,MAAI,CAAC,aAAc,QAAO,CAAC;AAC3B,QAAM,SACJ,kBAAkB,aAAa,CAAC,EAAG,YAAY,CAAC,KAAK,eAAe,aAAa,CAAC,CAAE;AACtF,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,WAAW,QAAQ;AAAA,IACvB;AAAA,EACF;AACA,MAAI,UAAU;AACZ,UAAM,SAAS,sBAAsB,SAAS,CAAC,CAAE;AACjD,QAAI,OAAQ,QAAO,CAAC,EAAE,GAAG,QAAQ,YAAY,SAAS,CAAC;AAAA,EACzD;AACA,QAAM,YAAY,QAAQ,MAAM,gCAAgC;AAChE,MAAI,WAAW;AACb,UAAM,YAAY,QAAQ,MAAM,kBAAkB;AAClD,UAAM,UAAU,QAAQ,MAAM,oCAAoC;AAClE,WAAO;AAAA,MACL;AAAA,QACE,MAAM,UAAU,CAAC;AAAA,QACjB,MAAM,YAAY,OAAO,UAAU,CAAC,CAAC,IAAI;AAAA,QACzC,UAAU,UAAU,CAAC,KAAK;AAAA,QAC1B;AAAA,QACA,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,EAAE,MAAM,GAAG,MAAM,YAAY,UAAU,IAAI,QAAQ,eAAe,WAAW,YAAY,SAAS;AAAA,EACpG;AACF;AAEO,IAAM,gBAAgB,EAAE,MAAM,WAAW,OAAAA,OAAM;;;ACxDtD,IAAM,mBAA2C;AAAA,EAC/C,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,kBAAkB;AACpB;AAKA,eAAsBC,OAAM,YAAyC;AACnE,QAAM,WAAW,MAAM,UAAU,YAAY;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,SAAU,QAAO,CAAC;AACvB,QAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,cAAc,QAAQ,MAAM,kCAAkC;AACpE,MAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,QAAM,SAAS,iBAAiB,YAAY,CAAC,EAAG,YAAY,CAAC;AAC7D,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,WAAW,QAAQ;AAAA,IACvB;AAAA,EACF;AACA,MAAI,UAAU;AACZ,UAAM,SAAS,sBAAsB,SAAS,CAAC,CAAE;AACjD,QAAI,OAAQ,QAAO,CAAC,EAAE,GAAG,QAAQ,YAAY,SAAS,CAAC;AAAA,EACzD;AAEA,QAAM,OAAO,QAAQ,MAAM,gCAAgC,IAAI,CAAC;AAChE,MAAI,MAAM;AACR,UAAM,OAAO,QAAQ,MAAM,kBAAkB,IAAI,CAAC;AAClD,UAAM,WAAW,QAAQ,MAAM,oCAAoC,IAAI,CAAC,KAAK;AAC7E,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,MAAM,OAAO,OAAO,IAAI,IAAI;AAAA,QAC5B;AAAA,QACA;AAAA,QACA,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,EAAE,MAAM,GAAG,MAAM,SAAS,UAAU,IAAI,QAAQ,eAAe,WAAW,YAAY,SAAS,CAAC;AAC1G;AAEO,IAAM,aAAa,EAAE,MAAM,QAAQ,OAAAA,OAAM;;;AC1DhD,OAAOC,YAAU;AAajB,eAAsBC,OAAM,YAAyC;AACnE,aAAW,aAAa,CAAC,kBAAkB,kBAAkB,eAAe,GAAG;AAC7E,UAAM,MAAMC,OAAK,KAAK,YAAY,SAAS;AAC3C,QAAI,CAAE,MAAM,OAAO,GAAG,EAAI;AAC1B,UAAM,MAAM,UAAU,SAAS,OAAO,IAClC,MAAM,SAA4C,GAAG,IACrD,MAAM,SAA4C,GAAG;AACzD,UAAM,UAAU,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAE/C,UAAM,MAAkB,CAAC;AACzB,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,OAAO,QAAQ,CAAC,MAAM,KAAM;AACjC,YAAM,SAAS,eAAe,MAAM,IAAI;AACxC,UAAI,CAAC,OAAQ;AACb,UAAI,KAAK;AAAA,QACP,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,UAAU,MAAM,YAAY;AAAA,QAC5B;AAAA,QACA,eAAe;AAAA,QACf,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,QAAI,IAAI,SAAS,EAAG,QAAO;AAAA,EAC7B;AACA,SAAO,CAAC;AACV;AAEO,IAAM,kBAAkB,EAAE,MAAM,aAAa,OAAAD,OAAM;;;ACpC1D,eAAsBE,OAAM,YAAyC;AACnE,QAAM,WAAW,MAAM,UAAU,YAAY;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,SAAU,QAAO,CAAC;AACvB,QAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,QAAQ,QAAQ,MAAM,6CAA6C;AACzE,QAAM,OAAO,QAAQ,MAAM,CAAC,IAAK;AAEjC,QAAM,YAAY,KAAK,MAAM,gCAAgC;AAC7D,QAAM,OAAO,KAAK,MAAM,gCAAgC,IAAI,CAAC;AAC7D,MAAI,CAAC,aAAa,CAAC,KAAM,QAAO,CAAC;AAEjC,QAAM,SAAS,eAAe,UAAU,CAAC,CAAE;AAC3C,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,OAAO,KAAK,MAAM,kBAAkB,IAAI,CAAC;AAC/C,QAAM,WAAW,KAAK,MAAM,oCAAoC,IAAI,CAAC,KAAK;AAE1E,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA,MAAM,OAAO,OAAO,IAAI,IAAI;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAEO,IAAM,gBAAgB,EAAE,MAAM,WAAW,OAAAA,OAAM;;;ACzCtD,OAAOC,YAAU;AAgBjB,eAAsBC,OAAM,YAAyC;AACnE,QAAM,aAAaC,OAAK,KAAK,YAAY,UAAU,aAAa;AAChE,MAAI,CAAE,MAAM,OAAO,UAAU,EAAI,QAAO,CAAC;AACzC,QAAM,MAAM,MAAM,SAA0B,UAAU;AAEtD,QAAM,MAAkB,CAAC;AACzB,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,OAAO,OAAO,GAAG,GAAG;AACtC,QAAI,CAAC,OAAO,WAAW,CAAC,MAAM,KAAM;AACpC,UAAM,SAAS,eAAe,MAAM,OAAO;AAC3C,QAAI,CAAC,OAAQ;AACb,UAAM,MAAM,GAAG,MAAM,MAAM,MAAM,IAAI,IAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,YAAY,EAAE;AACjF,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK;AAAA,MACP,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM,YAAY;AAAA,MAC5B;AAAA,MACA,eAAe;AAAA,MACf,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,IAAM,kBAAkB,EAAE,MAAM,aAAa,OAAAD,OAAM;;;AC1C1D,OAAOE,YAAU;AAcjB,SAAS,gBAAgB,KAAyC;AAChE,aAAW,OAAO,IAAI,SAAS,CAAC,GAAG;AACjC,UAAM,MAAM,OAAO,GAAG;AAEtB,UAAM,OAAO,IAAI,MAAM,GAAG,EAAE,IAAI;AAChC,UAAM,IAAI,OAAO,IAAI;AACrB,QAAI,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,QAAO;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,KAA6B;AACpD,QAAM,MAAM,IAAI;AAChB,QAAM,MAAM,CAAC,QAAoC;AAC/C,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,iBAAW,QAAQ,KAAK;AACtB,cAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,GAAG;AAC7B,YAAI,MAAM,IAAK,QAAO;AAAA,MACxB;AACA,aAAO;AAAA,IACT;AACA,WAAO,IAAI,GAAG;AAAA,EAChB;AACA,SAAO,IAAI,aAAa,KAAK,IAAI,gBAAgB,KAAK,IAAI,uBAAuB,KAAK;AACxF;AAKA,eAAsBC,OAAM,YAAyC;AACnE,aAAW,QAAQ,CAAC,sBAAsB,qBAAqB,GAAG;AAChE,UAAM,MAAMC,OAAK,KAAK,YAAY,IAAI;AACtC,QAAI,CAAE,MAAM,OAAO,GAAG,EAAI;AAC1B,UAAM,MAAM,MAAM,SAAsB,GAAG;AAC3C,QAAI,CAAC,KAAK,SAAU,QAAO,CAAC;AAE5B,UAAM,MAAkB,CAAC;AACzB,eAAW,CAAC,aAAa,GAAG,KAAK,OAAO,QAAQ,IAAI,QAAQ,GAAG;AAC7D,UAAI,CAAC,IAAI,MAAO;AAChB,YAAM,OAAO,gBAAgB,IAAI,KAAK;AACtC,UAAI,CAAC,KAAM;AACX,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,MAAM,gBAAgB,GAAG;AAAA,QACzB,UAAU,gBAAgB,GAAG;AAAA,QAC7B,QAAQ,KAAK;AAAA,QACb,eAAe,KAAK;AAAA,QACpB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AACA,SAAO,CAAC;AACV;AAEO,IAAM,sBAAsB,EAAE,MAAM,kBAAkB,OAAAD,OAAM;;;AVd5D,IAAM,aAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,qBAAqB,QAAoC;AAChE,SAAO,YAAY,EAChB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,YAAY,EAAE,iBAAiB,EAAE;AACpE;AAEA,SAAS,eAAe,QAAgC;AACtD,SAAO;AAAA,IACL,IAAIE,YAAW,OAAO,IAAI;AAAA,IAC1B,MAAMC,UAAS;AAAA,IACf,MAAM,OAAO,YAAY,OAAO;AAAA,IAChC,QAAQ,OAAO;AAAA,IACf,eAAe,OAAO;AAAA,IACtB,mBAAmB,qBAAqB,OAAO,MAAM;AAAA,IACrD,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,EACf;AACF;AAEO,SAAS,wBACd,SACA,SACM;AACN,QAAM,OAAO,EAAE,GAAI,QAAQ,IAAI,gBAAgB,CAAC,GAAI,GAAI,QAAQ,IAAI,mBAAmB,CAAC,EAAG;AAC3F,QAAM,oBAAmE,CAAC;AAC1E,QAAM,OAAO,oBAAI,IAAY;AAI7B,aAAW,UAAU,SAAS;AAC5B,eAAW,QAAQ,YAAY,GAAG;AAChC,UAAI,KAAK,WAAW,OAAO,OAAQ;AACnC,YAAM,kBAAkB,aAAa,KAAK,KAAK,MAAM,CAAC;AACtD,UAAI,CAAC,gBAAiB;AACtB,YAAM,SAAS;AAAA,QACb,KAAK;AAAA,QACL;AAAA,QACA,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AACA,UAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,cAAM,MAAM,iBAAiB,KAAK,MAAM,IAAI,eAAe,IAAI,OAAO,MAAM,IAAI,OAAO,aAAa;AACpG,YAAI,KAAK,IAAI,GAAG,EAAG;AACnB,aAAK,IAAI,GAAG;AACZ,0BAAkB,KAAK;AAAA,UACrB,MAAM;AAAA,UACN,QAAQ,KAAK;AAAA,UACb,eAAe;AAAA,UACf,QAAQ,OAAO;AAAA,UACf,eAAe,OAAO;AAAA,UACtB,QAAQ,OAAO;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAIA,QAAM,oBAAoB,QAAQ,KAAK,cAAc,QAAQ,IAAI,SAAS;AAC1E,aAAW,cAAc,sBAAsB,GAAG;AAChD,UAAM,WAAW,aAAa,KAAK,WAAW,OAAO,CAAC;AACtD,QAAI,CAAC,SAAU;AACf,UAAM,SAAS,0BAA0B,YAAY,UAAU,iBAAiB;AAChF,QAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,YAAM,MAAM,eAAe,WAAW,OAAO,IAAI,QAAQ,IAAI,qBAAqB,EAAE;AACpF,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,wBAAkB,KAAK;AAAA,QACrB,MAAM;AAAA,QACN,SAAS,WAAW;AAAA,QACpB,gBAAgB;AAAA,QAChB,qBAAqB,OAAO,uBAAuB,WAAW;AAAA,QAC9D,GAAI,oBAAoB,EAAE,oBAAoB,kBAAkB,IAAI,CAAC;AAAA,QACrE,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,aAAW,YAAY,iBAAiB,GAAG;AACzC,UAAM,WAAW,aAAa,KAAK,SAAS,OAAO,CAAC;AACpD,QAAI,CAAC,SAAU;AACf,UAAM,kBAAkB,aAAa,KAAK,SAAS,SAAS,IAAI,CAAC;AACjE,UAAM,SAAS,qBAAqB,UAAU,UAAU,eAAe;AACvE,QAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,YAAM,MAAM,oBAAoB,SAAS,OAAO,IAAI,QAAQ,IAAI,SAAS,SAAS,IAAI,IAAI,mBAAmB,SAAS;AACtH,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,wBAAkB,KAAK;AAAA,QACrB,MAAM;AAAA,QACN,SAAS,SAAS;AAAA,QAClB,gBAAgB;AAAA,QAChB,UAAU,SAAS;AAAA,QACnB,GAAI,kBAAkB,EAAE,cAAc,gBAAgB,IAAI,CAAC;AAAA,QAC3D,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,aAAW,QAAQ,eAAe,GAAG;AACnC,UAAM,WAAW,aAAa,KAAK,KAAK,OAAO,CAAC;AAChD,QAAI,aAAa,OAAW;AAC5B,UAAM,SAAS,mBAAmB,MAAM,QAAQ;AAChD,QAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,YAAM,MAAM,kBAAkB,KAAK,OAAO,IAAI,QAAQ;AACtD,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,wBAAkB,KAAK;AAAA,QACrB,MAAM;AAAA,QACN,SAAS,KAAK;AAAA,QACd,gBAAgB;AAAA,QAChB,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,kBAAkB,SAAS,EAAG,SAAQ,KAAK,oBAAoB;AACrE;AAMA,eAAsB,sBACpB,OACA,UACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAS,oBAAI,IAAsB;AACzC,eAAW,UAAU,YAAY;AAC/B,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,OAAO,MAAM,QAAQ,GAAG;AAAA,MAC1C,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACN,UAAU,OAAO,IAAI,qBAAqB,QAAQ,KAAK,IAAI,KAAM,IAAc,OAAO;AAAA,QACxF;AACA;AAAA,MACF;AACA,iBAAW,UAAU,SAAS;AAC5B,YAAI,CAAC,OAAO,KAAM;AAClB,YAAI,CAAC,OAAO,IAAI,OAAO,IAAI,EAAG,QAAO,IAAI,OAAO,MAAM,MAAM;AAAA,MAC9D;AAAA,IACF;AAEA,UAAM,aAAa,CAAC,GAAG,OAAO,OAAO,CAAC;AACtC,eAAW,UAAU,YAAY;AAC/B,YAAM,SAAS,eAAe,MAAM;AACpC,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAE,GAAG;AAC7B,cAAM,QAAQ,OAAO,IAAI,EAAE,GAAG,QAAQ,eAAe,SAAS,CAAC;AAC/D;AAAA,MACF,OAAO;AAIL,cAAM,WAAW,MAAM,kBAAkB,OAAO,EAAE;AAClD,cAAM,sBACJ,SAAS,kBAAkB,SAAS,WAAW;AACjD,cAAM,sBAAsB,OAAO,IAAI;AAAA,UACrC,GAAG;AAAA,UACH,GAAG;AAAA,UACH,eAAe;AAAA,QACjB,CAAC;AAAA,MACH;AAIA,YAAM,gBAAgBC,SAAQC,OAAK,SAAS,QAAQ,KAAK,OAAO,UAAU,CAAC;AAC3E,YAAM,EAAE,YAAY,YAAY,IAAI,YAAY,GAAG,IAAI;AAAA,QACrD;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb;AAAA,MACF;AACA,oBAAc;AACd,oBAAc;AACd,YAAM,eAAeD,SAAQC,OAAK,SAAS,UAAU,OAAO,UAAU,CAAC;AACvE,YAAM,OAAkB;AAAA,QACtB,IAAIC,iBAAW,YAAY,OAAO,IAAIC,UAAS,WAAW;AAAA,QAC1D,QAAQ;AAAA,QACR,QAAQ,OAAO;AAAA,QACf,MAAMA,UAAS;AAAA,QACf,YAAYC,YAAW;AAAA,QACvB,YAAYC,wBAAuB,YAAY;AAAA,QAC/C,UAAU,EAAE,MAAM,aAAa;AAAA,MACjC;AACA,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,cAAM,eAAe,KAAK,IAAI,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC5D;AAAA,MACF;AAAA,IACF;AAgBA,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,UAAU,WAAW,CAAC;AAC5B,cAAQ,KAAK,qBAAqB,QAAQ,OACtC,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI,KAC/B,QAAQ;AAMZ,YAAM,UAAUJ,OAAK,SAAS,UAAU,QAAQ,UAAU;AAC1D,YAAM,QAAQ,SAAS,OAAO;AAC9B,UAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,cAAM,UAAsB;AAAA,UAC1B,IAAI;AAAA,UACJ,MAAMF,UAAS;AAAA,UACf,MAAME,OAAK,SAAS,QAAQ,UAAU;AAAA,UACtC,MAAM;AAAA,UACN,UAAU,aAAaA,OAAK,SAAS,QAAQ,UAAU,CAAC,EAAE,YAAY;AAAA,QACxE;AACA,cAAM,QAAQ,OAAO,OAAO;AAC5B;AAAA,MACF;AACA,YAAM,UAAqB;AAAA,QACzB,IAAIC,iBAAW,QAAQ,KAAK,IAAI,OAAOC,UAAS,aAAa;AAAA,QAC7D,QAAQ,QAAQ,KAAK;AAAA,QACrB,QAAQ;AAAA,QACR,MAAMA,UAAS;AAAA,QACf,YAAYC,YAAW;AAAA,QACvB,YAAYC,wBAAuB,YAAY;AAAA,QAC/C,UAAU,EAAE,MAAML,SAAQ,OAAO,EAAE;AAAA,MACrC;AACA,UAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE,GAAG;AAC9B,cAAM,eAAe,QAAQ,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AACxE;AAAA,MACF;AAAA,IACF;AAIA,4BAAwB,SAAS,UAAU;AAC3C,QAAI,MAAM,QAAQ,QAAQ,KAAK,EAAE,GAAG;AAIlC,YAAM,UAAU,MAAM,kBAAkB,QAAQ,KAAK,EAAE;AACvD,YAAM,UAAuB;AAAA,QAC3B,GAAG;AAAA,QACH,GAAI,QAAQ;AAAA,QACZ,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACxD;AAKA,UAAI,CAAC,QAAQ,KAAK,qBAAqB,QAAQ,KAAK,kBAAkB,WAAW,GAAG;AAClF,eAAQ,QAA4C;AAAA,MACtD;AAIA,UAAI,CAAC,QAAQ,KAAK,oBAAoB;AACpC,eAAQ,QAA6C;AAAA,MACvD;AACA,YAAM,sBAAsB,QAAQ,KAAK,IAAI,OAA+B;AAAA,IAC9E;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AW5VA,SAAS,YAAYM,YAAU;AAC/B,OAAOC,YAAU;AAEjB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,YAAAC;AAAA,EACA,0BAAAC;AAAA,OACK;AAeP,eAAsB,gBAAgB,KAAgC;AACpE,QAAM,MAAgB,CAAC;AACvB,iBAAeC,MAAK,SAAgC;AAClD,UAAM,UAAU,MAAMC,KAAG,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AACjE,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAOC,OAAK,KAAK,SAAS,MAAM,IAAI;AAC1C,UAAI,MAAM,YAAY,GAAG;AACvB,YAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAI,MAAM,gBAAgB,IAAI,EAAG;AACjC,cAAMF,MAAK,IAAI;AAAA,MACjB,WAAW,MAAM,OAAO,KAAK,aAAa,MAAM,IAAI,EAAE,OAAO;AAC3D,YAAI,KAAK,IAAI;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACA,QAAMA,MAAK,GAAG;AACd,SAAO;AACT;AAIA,eAAsB,eACpB,OACA,UACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AACjB,aAAW,WAAW,UAAU;AAC9B,UAAM,cAAc,MAAM,gBAAgB,QAAQ,GAAG;AACrD,eAAW,QAAQ,aAAa;AAC9B,YAAM,UAAUE,OAAK,SAAS,UAAU,IAAI;AAC5C,YAAM,OAAmB;AAAA,QACvB,IAAIC,UAAS,OAAO;AAAA,QACpB,MAAMC,UAAS;AAAA,QACf,MAAMF,OAAK,SAAS,IAAI;AAAA,QACxB,MAAM;AAAA,QACN,UAAU,aAAaA,OAAK,SAAS,IAAI,CAAC,EAAE;AAAA,MAC9C;AACA,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,cAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,MACF;AAIA,YAAM,eAAeG,SAAQH,OAAK,SAAS,QAAQ,KAAK,IAAI,CAAC;AAC7D,YAAM,EAAE,YAAY,YAAY,IAAI,YAAY,GAAG,IAAI;AAAA,QACrD;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb;AAAA,MACF;AACA,oBAAc;AACd,oBAAc;AACd,YAAM,OAAkB;AAAA,QACtB,IAAII,iBAAW,YAAY,KAAK,IAAIC,UAAS,aAAa;AAAA,QAC1D,QAAQ;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,MAAMA,UAAS;AAAA,QACf,YAAYC,YAAW;AAAA,QACvB,YAAYC,wBAAuB,YAAY;AAAA,QAC/C,UAAU,EAAE,MAAM,QAAQ,MAAMP,OAAK,GAAG,EAAE,KAAK,GAAG,EAAE;AAAA,MACtD;AACA,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,cAAM,eAAe,KAAK,IAAI,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC5D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AC/FA,OAAOQ,YAAU;AACjB,OAAOC,aAAY;AACnB,OAAOC,iBAAgB;AAEvB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,mBAAAC;AAAA,EACA;AAAA,OACK;AAmBP,IAAMC,eAAc;AAEpB,SAASC,aAAY,QAAgB,QAA6B;AAChE,SAAO,OAAO;AAAA,IAAM,CAAC,UACnB,SAAS,OAAO,SAAS,KAAK,OAAO,MAAM,OAAO,QAAQD,YAAW;AAAA,EACvE;AACF;AAEA,SAASE,gBAAuB;AAC9B,QAAM,IAAI,IAAIC,QAAO;AACrB,IAAE,YAAYC,WAAU;AACxB,SAAO;AACT;AAIA,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,mBAAmB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS,CAAC;AAE7F,IAAM,sBAAsB,oBAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,QAAQ,OAAO,MAAM,CAAC;AAe3E,SAAS,qBAAqB,KAAqB;AACxD,MAAI,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,EAAG,MAAM,GAAG,EAAE,CAAC;AACvC,MAAI,CAAC,EAAE,WAAW,GAAG,EAAG,KAAI,MAAM;AAClC,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACtD,SAAO;AACT;AAMA,SAAS,iBAAiB,KAAsB;AAC9C,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,IAAI,SAAS,GAAG,EAAG,QAAO;AAC9B,MAAI,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,GAAG,EAAG,QAAO;AACvD,MAAI,QAAQ,KAAK,GAAG,EAAG,QAAO;AAC9B,MAAI,kEAAkE,KAAK,GAAG,EAAG,QAAO;AACxF,MAAI,mBAAmB,KAAK,GAAG,EAAG,QAAO;AACzC,SAAO;AACT;AAQO,SAAS,sBAAsB,KAAqB;AACzD,QAAM,YAAY,qBAAqB,GAAG;AAC1C,QAAM,WAAW,UAAU,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAChE,QAAM,aAAa,SAAS,IAAI,CAAC,QAAS,iBAAiB,GAAG,IAAI,WAAW,IAAI,YAAY,CAAE;AAC/F,SAAO,MAAM,WAAW,KAAK,GAAG;AAClC;AAIA,SAAS,KAAK,MAAyB,OAA6C;AAClF,QAAM,IAAI;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,MAAK,OAAO,KAAK;AAAA,EAC9B;AACF;AAIA,SAAS,iBAAiB,MAAwC;AAChE,MAAI,KAAK,SAAS,UAAU;AAC1B,aAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,YAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,UAAI,OAAO,SAAS,kBAAmB,QAAO,MAAM;AAAA,IACtD;AAEA,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,mBAAmB;AAEnC,aAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAI,KAAK,WAAW,CAAC,GAAG,SAAS,wBAAyB,QAAO;AAAA,IACnE;AACA,UAAM,MAAM,KAAK;AACjB,WAAO,IAAI,UAAU,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI;AAAA,EAC9C;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,SAA4B,KAA4B;AAChF,WAAS,IAAI,GAAG,IAAI,QAAQ,iBAAiB,KAAK;AAChD,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAM,IAAI,KAAK,kBAAkB,KAAK;AACtC,QAAI,CAAC,EAAG;AACR,UAAM,QAAQ,EAAE,SAAS,WAAW,iBAAiB,CAAC,IAAI,EAAE;AAC5D,QAAI,UAAU,IAAK;AACnB,UAAM,IAAI,KAAK,kBAAkB,OAAO;AACxC,QAAI,EAAG,QAAO,iBAAiB,CAAC;AAAA,EAClC;AACA,SAAO;AACT;AAIA,SAAS,oBAAoB,SAAsC;AACjE,WAAS,IAAI,GAAG,IAAI,QAAQ,iBAAiB,KAAK;AAChD,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAM,IAAI,KAAK,kBAAkB,KAAK;AACtC,UAAM,QAAQ,IAAK,EAAE,SAAS,WAAW,iBAAiB,CAAC,IAAI,EAAE,OAAQ;AACzE,QAAI,UAAU,SAAU;AACxB,UAAM,IAAI,KAAK,kBAAkB,OAAO;AACxC,QAAI,CAAC,EAAG,QAAO,CAAC;AAChB,QAAI,EAAE,SAAS,YAAY,EAAE,SAAS,mBAAmB;AACvD,YAAM,IAAI,iBAAiB,CAAC;AAC5B,aAAO,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC;AAAA,IAClC;AACA,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,MAAgB,CAAC;AACvB,eAAS,IAAI,GAAG,IAAI,EAAE,iBAAiB,KAAK;AAC1C,cAAM,KAAK,EAAE,WAAW,CAAC;AACzB,YAAI,OAAO,GAAG,SAAS,YAAY,GAAG,SAAS,oBAAoB;AACjE,gBAAM,IAAI,iBAAiB,EAAE;AAC7B,cAAI,EAAG,KAAI,KAAK,EAAE,YAAY,CAAC;AAAA,QACjC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAWO,SAAS,uBACd,QACA,QACA,YACA,YACkB;AAClB,QAAM,OAAOH,aAAY,QAAQ,MAAM;AACvC,QAAM,MAAwB,CAAC;AAC/B,QAAM,YAAY,aAAa,YAAY;AAC3C,OAAK,KAAK,UAAU,CAAC,SAAS;AAC5B,QAAI,KAAK,SAAS,kBAAmB;AACrC,UAAM,KAAK,KAAK,kBAAkB,UAAU;AAC5C,QAAI,CAAC,MAAM,GAAG,SAAS,oBAAqB;AAC5C,UAAM,OAAO,GAAG,kBAAkB,UAAU;AAC5C,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,KAAK,KAAK,YAAY;AACrC,UAAM,OAAO,KAAK,kBAAkB,WAAW;AAC/C,UAAM,QAAQ,MAAM,WAAW,CAAC;AAChC,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,KAAK,cAAc,MAAM;AAEtC,QAAI,eAAe,IAAI,MAAM,GAAG;AAC9B,YAAM,IAAI,iBAAiB,KAAK;AAChC,UAAI,KAAK,EAAE,WAAW,GAAG,GAAG;AAC1B,YAAI,KAAK;AAAA,UACP,QAAQ,WAAW,QAAQ,QAAQ,OAAO,YAAY;AAAA,UACtD,cAAc,qBAAqB,CAAC;AAAA,UACpC;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAGA,QAAI,WAAW,WAAW,cAAc,MAAM,SAAS,UAAU;AAC/D,YAAM,MAAM,iBAAiB,OAAO,KAAK;AACzC,UAAI,CAAC,OAAO,CAAC,IAAI,WAAW,GAAG,EAAG;AAClC,YAAM,UAAU,oBAAoB,KAAK;AACzC,YAAM,OAAO,QAAQ,SAAS,IAAI,UAAU,CAAC,KAAK;AAClD,iBAAW,KAAK,MAAM;AACpB,YAAI,KAAK;AAAA,UACP,QAAQ,MAAM,QAAQ,QAAQ,EAAE,YAAY;AAAA,UAC5C,cAAc,qBAAqB,GAAG;AAAA,UACtC;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAIA,SAAS,WAAW,SAA2B;AAC7C,SAAOI,SAAQ,OAAO,EAAE,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/D;AAIO,SAAS,mBAAmB,SAA0B;AAC3D,QAAM,OAAO,WAAW,OAAO;AAC/B,MAAI,CAAC,KAAK,SAAS,KAAK,EAAG,QAAO;AAClC,QAAM,OAAO,KAAK,KAAK,SAAS,CAAC,KAAK;AACtC,SAAO,qCAAqC,KAAK,IAAI;AACvD;AAIO,SAAS,mBAAmB,SAA0B;AAC3D,QAAM,OAAO,WAAW,OAAO;AAC/B,QAAM,WAAW,KAAK,QAAQ,OAAO;AACrC,MAAI,aAAa,MAAM,KAAK,WAAW,CAAC,MAAM,MAAO,QAAO;AAC5D,QAAM,OAAO,KAAK,KAAK,SAAS,CAAC,KAAK;AACtC,MAAI,gCAAgC,KAAK,IAAI,EAAG,QAAO;AACvD,SAAO,oBAAoB,IAAIC,OAAK,QAAQ,IAAI,CAAC;AACnD;AAKA,SAAS,YAAY,KAA4B;AAC/C,MAAI,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,EAAG,QAAO;AACrD,QAAM,WAAW,IAAI,MAAM,yBAAyB;AACpD,MAAI,SAAU,QAAO,MAAM,SAAS,CAAC;AACrC,QAAM,UAAU,IAAI,MAAM,aAAa;AACvC,MAAI,QAAS,QAAO,MAAM,QAAQ,CAAC;AACnC,SAAO;AACT;AAIA,SAAS,oBAAoB,SAAyB;AACpD,QAAM,OAAO,WAAW,OAAO;AAC/B,QAAM,SAAS,KAAK,YAAY,KAAK;AACrC,QAAM,UAAU,KAAK,MAAM,SAAS,GAAG,KAAK,SAAS,CAAC;AACtD,QAAM,QAAkB,CAAC;AACzB,aAAW,OAAO,SAAS;AACzB,UAAM,SAAS,YAAY,GAAG;AAC9B,QAAI,WAAW,KAAM,OAAM,KAAK,MAAM;AAAA,EACxC;AACA,SAAO,MAAM,MAAM,KAAK,GAAG;AAC7B;AAIA,SAAS,yBAAyB,SAAyB;AACzD,QAAM,OAAO,WAAW,OAAO;AAC/B,QAAM,WAAW,KAAK,QAAQ,OAAO;AACrC,QAAM,OAAO,KAAK,MAAM,WAAW,CAAC;AACpC,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,MAAM,KAAK,CAAC;AAChB,QAAI,MAAM,KAAK,SAAS,GAAG;AACzB,YAAM,IAAI,QAAQ,gCAAgC,EAAE;AACpD,UAAI,QAAQ,QAAS;AAAA,IACvB;AACA,UAAM,SAAS,YAAY,GAAG;AAC9B,QAAI,WAAW,KAAM,OAAM,KAAK,MAAM;AAAA,EACxC;AACA,SAAO,MAAM,MAAM,KAAK,GAAG;AAC7B;AAIA,SAAS,eAAe,MAA6D;AACnF,QAAM,MAA0C,CAAC;AACjD,OAAK,MAAM,CAAC,SAAS;AACnB,QAAI,KAAK,SAAS,mBAAoB;AACtC,UAAM,OAAO,KAAK,kBAAkB,aAAa;AACjD,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,KAAK,cAAc,MAAM;AACtC,QAAI,KAAK,SAAS,wBAAwB;AACxC,YAAM,OAAO,KAAK,kBAAkB,MAAM,GAAG;AAC7C,UAAI,QAAQ,iBAAiB,IAAI,IAAI,EAAG,KAAI,KAAK,EAAE,QAAQ,MAAM,KAAK,CAAC;AACvE;AAAA,IACF;AACA,QAAI,KAAK,SAAS,yBAAyB,KAAK,SAAS,wBAAwB;AAC/E,eAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,cAAM,IAAI,KAAK,WAAW,CAAC;AAC3B,YAAI,GAAG,SAAS,sBAAuB;AACvC,cAAM,OAAO,EAAE,kBAAkB,MAAM,GAAG;AAC1C,YAAI,QAAQ,iBAAiB,IAAI,IAAI,EAAG,KAAI,KAAK,EAAE,QAAQ,MAAM,KAAK,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,SAAS,mBACP,QACA,SACA,QACkB;AAClB,MAAI,mBAAmB,OAAO,GAAG;AAC/B,UAAM,OAAOL,aAAY,QAAQ,MAAM;AACvC,UAAM,WAAW,oBAAoB,OAAO;AAC5C,WAAO,eAAe,KAAK,QAAQ,EAAE,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO;AAAA,MAC9D;AAAA,MACA,cAAc,qBAAqB,QAAQ;AAAA,MAC3C;AAAA,MACA,WAAW;AAAA,IACb,EAAE;AAAA,EACJ;AACA,MAAI,mBAAmB,OAAO,GAAG;AAG/B,WAAO;AAAA,MACL;AAAA,QACE,QAAQ;AAAA,QACR,cAAc,qBAAqB,yBAAyB,OAAO,CAAC;AAAA,QACpE,MAAM;AAAA,QACN,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAIA,eAAsB,UACpB,OACA,UACqD;AACrD,QAAM,WAAWC,cAAa;AAC9B,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAO;AAAA,MACX,GAAI,QAAQ,IAAI,gBAAgB,CAAC;AAAA,MACjC,GAAI,QAAQ,IAAI,mBAAmB,CAAC;AAAA,IACtC;AACA,UAAM,aAAa,KAAK,SAAS,MAAM;AACvC,UAAM,aAAa,KAAK,SAAS,MAAM;AACvC,UAAM,UAAU,KAAK,MAAM,MAAM;AACjC,QAAI,CAAC,cAAc,CAAC,cAAc,CAAC,QAAS;AAE5C,UAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG;AAC/C,eAAW,QAAQ,OAAO;AAGxB,UAAI,WAAW,KAAK,IAAI,EAAG;AAC3B,UAAI,CAAC,oBAAoB,IAAII,OAAK,QAAQ,KAAK,IAAI,CAAC,EAAG;AACvD,YAAM,UAAUD,SAAQC,OAAK,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC;AAE7D,UAAI;AACJ,UAAI;AACF,YAAI,YAAY,mBAAmB,OAAO,KAAK,mBAAmB,OAAO,IAAI;AAC3E,mBAAS,mBAAmB,KAAK,SAAS,SAAS,QAAQ;AAAA,QAC7D,WAAW,cAAc,YAAY;AACnC,mBAAS,uBAAuB,KAAK,SAAS,UAAU,YAAY,UAAU;AAAA,QAChF,OAAO;AACL,mBAAS,CAAC;AAAA,QACZ;AAAA,MACF,SAAS,KAAK;AACZ,8BAAsB,oBAAoB,KAAK,MAAM,GAAG;AACxD;AAAA,MACF;AACA,UAAI,OAAO,WAAW,EAAG;AAEzB,iBAAW,SAAS,QAAQ;AAC1B,cAAM,MAAM,QAAQ,QAAQ,IAAI,MAAM,MAAM,QAAQ,MAAM,YAAY;AACtE,YAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,MAAMC,UAAS;AAAA,YACf,MAAM,GAAG,MAAM,MAAM,IAAI,MAAM,YAAY;AAAA,YAC3C,SAAS,QAAQ,IAAI;AAAA,YACrB,QAAQ,MAAM;AAAA,YACd,cAAc,MAAM;AAAA,YACpB,MAAM;AAAA,YACN,MAAM,MAAM;AAAA,YACZ,WAAW,MAAM;AAAA,YACjB,eAAe;AAAA,UACjB;AACA,gBAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,QACF;AAIA,cAAM,aAAaC,iBAAgB,QAAQ,KAAK,IAAI,KAAKC,UAAS,QAAQ;AAC1E,YAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,QAAQ,QAAQ,KAAK;AAAA,YACrB,QAAQ;AAAA,YACR,MAAMA,UAAS;AAAA,YACf,YAAYC,YAAW;AAAA,YACvB,YAAYC,wBAAuB,YAAY;AAAA,YAC/C,UAAU;AAAA,cACR,MAAM;AAAA,cACN,MAAM,MAAM;AAAA,cACZ,SAAS,QAAQ,KAAK,SAAS,MAAM,IAAI;AAAA,YAC3C;AAAA,UACF;AACA,gBAAM,eAAe,YAAY,QAAQ,KAAK,IAAI,KAAK,IAAI;AAC3D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AC/cA,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AAEjB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,mBAAAC;AAAA,EACA,gBAAAC;AAAA,OACK;AAsBP,IAAM,kBAAkB;AAcxB,SAAS,UAAU,SAAgC;AACjD,QAAM,IAAI,QAAQ,MAAM,8CAA8C;AACtE,SAAO,IAAI,EAAE,CAAC,IAAK;AACrB;AAGA,SAAS,OAAO,SAAiB,QAAwB;AACvD,SAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,EAAE;AAC9C;AASO,SAAS,qBAAqB,SAAiB,WAAiD;AACrG,QAAM,MAA6B,CAAC;AACpC,QAAM,YAAY;AAClB,MAAI;AACJ,UAAQ,KAAK,UAAU,KAAK,OAAO,OAAO,MAAM;AAC9C,UAAM,cAAc,GAAG,CAAC;AACxB,UAAM,aAAa,YAAY,GAAG,SAAS,IAAI,WAAW,KAAK;AAG/D,UAAM,YAAY,UAAU;AAC5B,QAAI,QAAQ;AACZ,QAAI,IAAI;AACR,WAAO,IAAI,QAAQ,UAAU,QAAQ,GAAG,KAAK;AAC3C,YAAM,KAAK,QAAQ,CAAC;AACpB,UAAI,OAAO,IAAK;AAAA,eACP,OAAO,IAAK;AAAA,IACvB;AACA,UAAM,OAAO,QAAQ,MAAM,WAAW,IAAI,CAAC;AAC3C,UAAM,QAAQ;AACd,QAAI;AACJ,YAAQ,KAAK,MAAM,KAAK,IAAI,OAAO,MAAM;AACvC,YAAM,YAAY,GAAG,CAAC;AACtB,YAAM,OAAO,OAAO,SAAS,YAAY,GAAG,KAAK;AACjD,UAAI,KAAK,EAAE,YAAY,WAAW,KAAK,CAAC;AAAA,IAC1C;AAEA,cAAU,YAAY;AAAA,EACxB;AACA,SAAO;AACT;AAQA,eAAe,eAAe,KAAgC;AAC5D,QAAM,MAAgB,CAAC;AACvB,iBAAeC,MAAK,SAAgC;AAClD,UAAM,UAAU,MAAMC,KAAG,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AACjF,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAOC,OAAK,KAAK,SAAS,MAAM,IAAI;AAC1C,UAAI,MAAM,YAAY,GAAG;AACvB,YAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAI,MAAM,gBAAgB,IAAI,EAAG;AACjC,cAAMF,MAAK,IAAI;AAAA,MACjB,WAAW,MAAM,OAAO,KAAKE,OAAK,QAAQ,MAAM,IAAI,MAAM,iBAAiB;AACzE,YAAI,KAAK,IAAI;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACA,QAAMF,MAAK,GAAG;AACd,SAAO;AACT;AAIA,eAAsB,eACpB,OACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,aAAa,MAAM,eAAe,QAAQ,GAAG;AACnD,eAAW,aAAa,YAAY;AAGlC,UAAI,WAAW,SAAS,EAAG;AAC3B,YAAM,UAAUG,SAAQD,OAAK,SAAS,QAAQ,KAAK,SAAS,CAAC;AAE7D,UAAI;AACJ,UAAI;AACF,kBAAU,MAAMD,KAAG,SAAS,WAAW,MAAM;AAAA,MAC/C,SAAS,KAAK;AACZ,8BAAsB,oBAAoB,WAAW,GAAG;AACxD;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,kBAAU,qBAAqB,SAAS,UAAU,OAAO,CAAC;AAAA,MAC5D,SAAS,KAAK;AACZ,8BAAsB,oBAAoB,WAAW,GAAG;AACxD;AAAA,MACF;AACA,UAAI,QAAQ,WAAW,EAAG;AAE1B,iBAAW,UAAU,SAAS;AAC5B,cAAM,MAAMG,cAAa,OAAO,YAAY,OAAO,SAAS;AAC5D,YAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,gBAAM,OAAuB;AAAA,YAC3B,IAAI;AAAA,YACJ,MAAMC,WAAS;AAAA,YACf,MAAM,GAAG,OAAO,UAAU,IAAI,OAAO,SAAS;AAAA,YAC9C,YAAY,OAAO;AAAA,YACnB,WAAW,OAAO;AAAA,YAClB,MAAM;AAAA,YACN,MAAM,OAAO;AAAA,YACb,eAAe;AAAA,UACjB;AACA,gBAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,QACF;AAMA,cAAM,aAAaC,iBAAgB,QAAQ,KAAK,IAAI,KAAKC,UAAS,QAAQ;AAC1E,YAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,QAAQ,QAAQ,KAAK;AAAA,YACrB,QAAQ;AAAA,YACR,MAAMA,UAAS;AAAA,YACf,YAAYC,YAAW;AAAA,YACvB,YAAYC,wBAAuB,YAAY;AAAA,YAC/C,UAAU;AAAA,cACR,MAAM;AAAA,cACN,MAAM,OAAO;AAAA,cACb,SAAS,QAAQ,SAAS,OAAO,IAAI;AAAA,YACvC;AAAA,UACF;AACA,gBAAM,eAAe,YAAY,QAAQ,KAAK,IAAI,KAAK,IAAI;AAC3D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;ACpMA;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,wBAAAC;AAAA,OACK;;;ACPP,OAAOC,YAAU;AACjB,OAAOC,aAAY;AACnB,OAAOC,iBAAgB;AACvB,OAAOC,aAAY;AAEnB;AAAA,EACE,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA;AAAA,OACK;AAoBP,IAAM,4BAA4B,oBAAI,IAAI,CAAC,mBAAmB,gBAAgB,CAAC;AAI/E,IAAM,yBAAyB,oBAAI,IAAI,CAAC,KAAK,QAAQ,WAAW,gBAAgB,QAAQ,CAAC;AAKzF,SAAS,wBAAwB,MAAkC;AACjE,MAAI,SAAmC,KAAK;AAG5C,SAAO,QAAQ;AACb,QAAI,OAAO,SAAS,iBAAiB;AAGnC,UAAI,QAAkC,OAAO;AAC7C,aAAO,SAAS,MAAM,SAAS,yBAAyB,MAAM,SAAS,4BAA4B;AACjG,gBAAQ,MAAM;AAAA,MAChB;AACA,UAAI,CAAC,MAAO,QAAO;AAGnB,YAAM,UAAU,MAAM,WAAW,CAAC;AAClC,YAAM,UAAU,SAAS,QAAQ;AAGjC,YAAM,QAAQ,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,IAAK;AAClE,aAAO,uBAAuB,IAAI,KAAK;AAAA,IACzC;AACA,aAAS,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAMA,SAAS,sBACP,MACA,KACM;AACN,MAAI,0BAA0B,IAAI,KAAK,IAAI,EAAG,KAAI,KAAK,EAAE,MAAM,KAAK,MAAM,KAAK,CAAC;AAChF,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,uBAAsB,OAAO,GAAG;AAAA,EAC7C;AACF;AAoBA,IAAMC,eAAc;AAEpB,SAASC,aAAY,QAAgB,QAA6B;AAChE,SAAO,OAAO;AAAA,IAAM,CAAC,UACnB,SAAS,OAAO,SAAS,KAAK,OAAO,MAAM,OAAO,QAAQD,YAAW;AAAA,EACvE;AACF;AAEO,SAAS,gBACd,QACA,QACA,YACgB;AAChB,QAAM,OAAOC,aAAY,QAAQ,MAAM;AACvC,QAAM,WAAwD,CAAC;AAC/D,wBAAsB,KAAK,UAAU,QAAQ;AAC7C,QAAM,MAAsB,CAAC;AAC7B,aAAW,OAAO,UAAU;AAI1B,QAAI,wBAAwB,IAAI,IAAI,EAAG;AACvC,eAAW,QAAQ,YAAY;AAG7B,UAAI,eAAe,IAAI,MAAM,IAAI,GAAG;AAClC,YAAI,KAAK,EAAE,MAAM,MAAM,IAAI,KAAK,cAAc,MAAM,EAAE,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAASC,gBAAuB;AAC9B,QAAM,IAAI,IAAIC,QAAO;AACrB,IAAE,YAAYC,WAAU;AACxB,SAAO;AACT;AAEA,SAASC,gBAAuB;AAC9B,QAAM,IAAI,IAAIF,QAAO;AACrB,IAAE,YAAYG,OAAM;AACpB,SAAO;AACT;AAmBA,eAAsB,iBACpB,OACA,UACqD;AACrD,QAAM,WAAWJ,cAAa;AAC9B,QAAM,WAAWG,cAAa;AAI9B,QAAM,EAAE,YAAY,aAAa,IAAI,sBAAsB,QAAQ;AAEnE,MAAI,aAAa;AACjB,MAAI,aAAa;AACjB,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG;AAG/C,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,QAAQ,OAAO;AAExB,UAAI,WAAW,KAAK,IAAI,EAAG;AAC3B,YAAM,SAASE,OAAK,QAAQ,KAAK,IAAI,MAAM,QAAQ,WAAW;AAC9D,UAAI;AACJ,UAAI;AACF,gBAAQ,gBAAgB,KAAK,SAAS,QAAQ,UAAU;AAAA,MAC1D,SAAS,KAAK;AACZ,8BAAsB,wBAAwB,KAAK,MAAM,GAAG;AAC5D;AAAA,MACF;AACA,UAAI,MAAM,WAAW,EAAG;AACxB,YAAM,UAAUC,SAAQD,OAAK,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC;AAC7D,iBAAW,QAAQ,OAAO;AACxB,cAAM,WAAW,aAAa,IAAI,KAAK,IAAI;AAC3C,YAAI,CAAC,YAAY,aAAa,QAAQ,KAAK,GAAI;AAC/C,cAAM,WAAW,GAAG,OAAO,IAAI,QAAQ;AACvC,YAAI,KAAK,IAAI,QAAQ,EAAG;AACxB,aAAK,IAAI,QAAQ;AAOjB,cAAM,aAAaE,wBAAuB,4BAA4B;AACtE,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,UACX,SAAS,QAAQ,KAAK,SAAS,KAAK,IAAI;AAAA,QAC1C;AAKA,cAAM,EAAE,YAAY,YAAY,GAAG,YAAY,EAAE,IAAI;AAAA,UACnD;AAAA,UACA,QAAQ,IAAI;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb;AAAA,QACF;AACA,sBAAc;AACd,sBAAc;AAId,YAAI,CAAC,qBAAqB,UAAU,GAAG;AACrC,+BAAqB;AAAA,YACnB,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,MAAMC,WAAS;AAAA,YACf;AAAA,YACA,gBAAgB;AAAA,YAChB,UAAU;AAAA,UACZ,CAAC;AACD;AAAA,QACF;AACA,cAAM,SAASC,iBAAW,YAAY,UAAUD,WAAS,KAAK;AAC9D,YAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,MAAMA,WAAS;AAAA,YACf,YAAYE,YAAW;AAAA,YACvB;AAAA,YACA,UAAU;AAAA,UACZ;AACA,gBAAM,eAAe,QAAQ,YAAY,UAAU,IAAI;AACvD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AC9PA,OAAOC,YAAU;AACjB,OAAOC,aAAY;AACnB,OAAOC,iBAAgB;AAEvB;AAAA,EACE,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,wBAAAC;AAAA,EACA,aAAAC;AAAA,OACK;AA+BP,IAAMC,eAAc;AAEpB,SAASC,aAAY,QAAgB,QAA6B;AAChE,SAAO,OAAO;AAAA,IAAM,CAAC,UACnB,SAAS,OAAO,SAAS,KAAK,OAAO,MAAM,OAAO,QAAQD,YAAW;AAAA,EACvE;AACF;AAEA,SAASE,gBAAuB;AAC9B,QAAM,IAAI,IAAIC,QAAO;AACrB,IAAE,YAAYC,WAAU;AACxB,SAAO;AACT;AAEA,IAAM,uBAAuB,oBAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,QAAQ,OAAO,MAAM,CAAC;AACnF,IAAM,gBAAgB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,WAAW,SAAS,CAAC;AAYrG,SAASC,MAAK,MAAyB,OAA6C;AAClF,QAAM,IAAI;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,MAAO,CAAAA,MAAK,OAAO,KAAK;AAAA,EAC9B;AACF;AAMA,SAAS,eAAe,MAAwC;AAC9D,MAAI,KAAK,SAAS,UAAU;AAC1B,aAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,YAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,UAAI,OAAO,SAAS,kBAAmB,QAAO,MAAM;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,mBAAmB;AACnC,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,YAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,UAAI,CAAC,MAAO;AACZ,UAAI,MAAM,SAAS,kBAAmB,QAAO,MAAM;AAAA,eAC1C,MAAM,SAAS,wBAAyB,QAAO;AAAA,IAC1D;AAEA,QAAI,IAAI,WAAW,GAAG;AACpB,YAAM,MAAM,KAAK;AACjB,aAAO,IAAI,UAAU,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,kBAAkB,SAAgD;AACzE,WAAS,IAAI,GAAG,IAAI,QAAQ,iBAAiB,KAAK;AAChD,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAM,IAAI,KAAK,kBAAkB,KAAK;AACtC,UAAM,QAAQ,IAAK,EAAE,SAAS,WAAW,WAAW,CAAC,IAAI,EAAE,OAAQ;AACnE,QAAI,UAAU,SAAU;AACxB,UAAM,IAAI,KAAK,kBAAkB,OAAO;AACxC,QAAI,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,oBAAoB;AAC9D,YAAM,IAAI,WAAW,CAAC;AACtB,aAAO,IAAI,EAAE,YAAY,IAAI;AAAA,IAC/B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAAwC;AAC1D,MAAI,KAAK,SAAS,UAAU;AAC1B,aAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,KAAK;AAC7C,YAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,UAAI,OAAO,SAAS,kBAAmB,QAAO,MAAM;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,kBAAkB,SAAsD;AAC/E,WAAS,IAAI,GAAG,IAAI,QAAQ,iBAAiB,KAAK;AAChD,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAM,IAAI,KAAK,kBAAkB,KAAK;AACtC,UAAM,QAAQ,IAAK,EAAE,SAAS,WAAW,WAAW,CAAC,IAAI,EAAE,OAAQ;AACnE,QAAI,UAAU,MAAO,QAAO,KAAK,kBAAkB,OAAO;AAAA,EAC5D;AACA,SAAO;AACT;AAIA,SAAS,OAAO,QAA+B;AAC7C,MAAI;AACF,UAAM,YAAY,OAAO,WAAW,IAAI,IAAI,QAAQ,MAAM,KAAK;AAC/D,UAAM,SAAS,IAAI,IAAI,SAAS;AAChC,WAAO,OAAO,YAAY;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,UAAU,QAAgB,YAAwC;AACzE,aAAW,QAAQ,YAAY;AAC7B,QAAI,eAAe,QAAQ,IAAI,EAAG,QAAO;AAAA,EAC3C;AACA,SAAO;AACT;AAOO,SAAS,0BACd,QACA,QACA,YACkB;AAClB,QAAM,OAAOJ,aAAY,QAAQ,MAAM;AACvC,QAAM,MAAwB,CAAC;AAE/B,QAAM,OAAO,CACX,SACA,QACA,aACS;AACT,UAAM,SAAS,eAAe,OAAO;AACrC,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,UAAU,QAAQ,UAAU;AACzC,QAAI,CAAC,KAAM;AACX,UAAM,IAAI,OAAO,MAAM;AACvB,QAAI,MAAM,KAAM;AAChB,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,QAAI,KAAK;AAAA,MACP;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA,SAAS,QAAQ,QAAQ,IAAI;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,EAAAI,MAAK,KAAK,UAAU,CAAC,SAAS;AAC5B,QAAI,KAAK,SAAS,kBAAmB;AACrC,UAAM,KAAK,KAAK,kBAAkB,UAAU;AAC5C,QAAI,CAAC,GAAI;AACT,UAAM,OAAO,KAAK,kBAAkB,WAAW;AAC/C,UAAM,QAAQ,MAAM,WAAW,CAAC;AAChC,QAAI,CAAC,MAAO;AAGZ,UAAM,SACJ,GAAG,SAAS,eACR,GAAG,OACH,GAAG,SAAS,sBACT,GAAG,kBAAkB,UAAU,GAAG,QAAQ,KAC3C;AAER,QAAI,GAAG,SAAS,gBAAgB,WAAW,SAAS;AAClD,YAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,YAAM,SAAS,QAAQ,KAAK,SAAS,WAAY,kBAAkB,IAAI,KAAK,QAAS;AACrF,WAAK,OAAO,QAAQ,IAAI;AACxB;AAAA,IACF;AAGA,QAAI,GAAG,SAAS,gBAAgB,WAAW,SAAS;AAClD,UAAI,MAAM,SAAS,UAAU;AAC3B,cAAM,UAAU,kBAAkB,KAAK;AACvC,YAAI,QAAS,MAAK,SAAS,kBAAkB,KAAK,KAAK,OAAO,IAAI;AAAA,MACpE,OAAO;AACL,cAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,cAAM,SAAS,QAAQ,KAAK,SAAS,WAAY,kBAAkB,IAAI,KAAK,QAAS;AACrF,aAAK,OAAO,QAAQ,IAAI;AAAA,MAC1B;AACA;AAAA,IACF;AAEA,QAAI,GAAG,SAAS,qBAAqB;AACnC,YAAM,MAAM,GAAG,kBAAkB,QAAQ;AACzC,YAAM,UAAU,KAAK,QAAQ;AAG7B,UAAI,YAAY,WAAW,cAAc,IAAI,MAAM,GAAG;AACpD,YAAI,WAAW,aAAa,MAAM,SAAS,UAAU;AACnD,gBAAM,UAAU,kBAAkB,KAAK;AACvC,cAAI,QAAS,MAAK,SAAS,kBAAkB,KAAK,KAAK,OAAO,IAAI;AAAA,QACpE,OAAO;AACL,eAAK,OAAO,OAAO,YAAY,GAAG,IAAI;AAAA,QACxC;AACA;AAAA,MACF;AAGA,WAAK,YAAY,UAAU,YAAY,aAAa,WAAW,aAAa,WAAW,QAAQ;AAC7F,cAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,cAAM,SACJ,QAAQ,KAAK,SAAS,WACjB,kBAAkB,IAAI,MAAM,WAAW,QAAQ,QAAQ,SACxD;AACN,aAAK,OAAO,QAAQ,IAAI;AACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAaA,SAAS,gBAAgB,OAA6C;AACpE,QAAM,QAAQ,oBAAI,IAA0B;AAC5C,QAAM,YAAY,CAAC,KAAK,UAAU;AAChC,UAAM,OAAO;AACb,QAAI,KAAK,SAASC,WAAS,UAAW;AACtC,UAAM,QAAQ;AACd,UAAM,QAAQC,WAAU,MAAM,OAAO;AACrC,UAAM,QAAoB;AAAA,MACxB,QAAQ,MAAM,OAAO,YAAY;AAAA,MACjC,gBAAgB,sBAAsB,MAAM,YAAY;AAAA,MACxD,aAAa,MAAM;AAAA,IACrB;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,KAAM,MAAK,KAAK,KAAK;AAAA,QACpB,OAAM,IAAI,OAAO,CAAC,KAAK,CAAC;AAAA,EAC/B,CAAC;AACD,SAAO;AACT;AAKA,SAAS,UACP,SACA,QACA,gBACwB;AACxB,SAAO,QAAQ;AAAA,IACb,CAAC,MACC,EAAE,mBAAmB,mBACpB,EAAE,WAAW,SAAS,WAAW,UAAa,EAAE,WAAW;AAAA,EAChE;AACF;AAEA,eAAsB,kBACpB,OACA,UACqD;AACrD,QAAM,WAAWL,cAAa;AAC9B,QAAM,EAAE,YAAY,aAAa,IAAI,sBAAsB,QAAQ;AACnE,QAAM,aAAa,gBAAgB,KAAK;AACxC,MAAI,WAAW,SAAS,EAAG,QAAO,EAAE,YAAY,GAAG,YAAY,EAAE;AAEjE,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG;AAG/C,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,QAAQ,OAAO;AAExB,UAAI,WAAW,KAAK,IAAI,EAAG;AAC3B,UAAI,CAAC,qBAAqB,IAAIM,OAAK,QAAQ,KAAK,IAAI,CAAC,EAAG;AAExD,UAAI;AACJ,UAAI;AACF,gBAAQ,0BAA0B,KAAK,SAAS,UAAU,UAAU;AAAA,MACtE,SAAS,KAAK;AACZ,8BAAsB,+BAA+B,KAAK,MAAM,GAAG;AACnE;AAAA,MACF;AACA,UAAI,MAAM,WAAW,EAAG;AAExB,YAAM,UAAUC,SAAQD,OAAK,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC;AAC7D,iBAAW,QAAQ,OAAO;AACxB,cAAM,kBAAkB,aAAa,IAAI,KAAK,IAAI;AAGlD,YAAI,CAAC,mBAAmB,oBAAoB,QAAQ,KAAK,GAAI;AAC7D,cAAM,UAAU,WAAW,IAAI,eAAe;AAC9C,YAAI,CAAC,QAAS;AACd,cAAM,iBAAiB,sBAAsB,KAAK,YAAY;AAC9D,cAAM,QAAQ,UAAU,SAAS,KAAK,QAAQ,cAAc;AAC5D,YAAI,CAAC,MAAO;AAEZ,cAAM,WAAW,GAAG,OAAO,IAAI,MAAM,WAAW;AAChD,YAAI,KAAK,IAAI,QAAQ,EAAG;AACxB,aAAK,IAAI,QAAQ;AAKjB,cAAM,EAAE,YAAY,YAAY,GAAG,YAAY,EAAE,IAAI;AAAA,UACnD;AAAA,UACA,QAAQ,IAAI;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb;AAAA,QACF;AACA,sBAAc;AACd,sBAAc;AAMd,cAAM,aAAaE,wBAAuB,oBAAoB;AAC9D,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,UACX,SAAS,KAAK;AAAA,UACd,QAAQ,KAAK,UAAU,MAAM;AAAA,UAC7B,cAAc,KAAK;AAAA,QACrB;AACA,YAAI,CAACC,sBAAqB,UAAU,GAAG;AACrC,+BAAqB;AAAA,YACnB,QAAQ;AAAA,YACR,QAAQ,MAAM;AAAA,YACd,MAAMC,WAAS;AAAA,YACf;AAAA,YACA,gBAAgB;AAAA,YAChB,UAAU;AAAA,UACZ,CAAC;AACD;AAAA,QACF;AACA,cAAM,SAASC,iBAAW,YAAY,MAAM,aAAaD,WAAS,KAAK;AACvE,YAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,QAAQ,MAAM;AAAA,YACd,MAAMA,WAAS;AAAA,YACf,YAAYE,aAAW;AAAA,YACvB;AAAA,YACA,UAAU;AAAA,UACZ;AACA,gBAAM,eAAe,QAAQ,YAAY,MAAM,aAAa,IAAI;AAChE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AC/ZA,OAAOC,YAAU;AACjB,SAAS,WAAAC,gBAAe;AAOxB,IAAM,oBACJ;AACF,IAAM,oBACJ;AAEF,SAAS,QAAQ,IAAY,MAAkD;AAC7E,KAAG,YAAY;AACf,QAAM,MAA0C,CAAC;AACjD,MAAI;AACJ,UAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;AACnC,QAAI,KAAK,EAAE,OAAO,EAAE,CAAC,GAAI,OAAO,EAAE,MAAM,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAEO,SAAS,uBACd,MACA,YACoB;AACpB,QAAM,MAA0B,CAAC;AACjC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAO,CAAC,OAAe,aAAqD;AAChF,UAAM,MAAM,GAAG,QAAQ,IAAI,KAAK;AAChC,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,UAAM,OAAO,OAAO,KAAK,SAAS,KAAK;AACvC,QAAI,KAAK;AAAA,MACP,SAASC,SAAQ,eAAe,KAAK;AAAA,MACrC,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA;AAAA;AAAA;AAAA,MAIA,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,MAAMC,OAAK,SAAS,YAAY,KAAK,IAAI;AAAA,QACzC;AAAA,QACA,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,aAAW,EAAE,MAAM,KAAK,QAAQ,mBAAmB,KAAK,OAAO,EAAG,MAAK,OAAO,cAAc;AAC5F,aAAW,EAAE,MAAM,KAAK,QAAQ,mBAAmB,KAAK,OAAO,EAAG,MAAK,OAAO,eAAe;AAC7F,SAAO;AACT;;;ACtDA,OAAOC,YAAU;AACjB,SAAS,WAAAC,gBAAe;AAMxB,IAAM,eAAe;AAEd,SAAS,uBACd,MACA,YACoB;AACpB,QAAM,MAA0B,CAAC;AACjC,QAAM,OAAO,oBAAI,IAAY;AAC7B,eAAa,YAAY;AACzB,MAAI;AACJ,UAAQ,IAAI,aAAa,KAAK,KAAK,OAAO,OAAO,MAAM;AACrD,UAAM,OAAO,EAAE,CAAC;AAChB,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,SAAK,IAAI,IAAI;AACb,UAAM,OAAO,OAAO,KAAK,SAAS,IAAI;AACtC,QAAI,KAAK;AAAA,MACP,SAASC,SAAQ,SAAS,IAAI;AAAA,MAC9B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA;AAAA;AAAA;AAAA,MAIV,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,MAAMC,OAAK,SAAS,YAAY,KAAK,IAAI;AAAA,QACzC;AAAA,QACA,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;ACvCA,OAAOC,YAAU;AACjB,SAAS,WAAAC,gBAAe;AASxB,IAAM,eAAe;AACrB,IAAM,kBAAkB;AAExB,SAAS,UAAU,MAAc,SAA4B;AAC3D,SAAO,QAAQ,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAC7C;AAEA,SAASC,SAAQ,IAAY,MAAiD;AAC5E,KAAG,YAAY;AACf,QAAM,MAAyC,CAAC;AAChD,MAAI;AACJ,UAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;AACnC,QAAI,KAAK,EAAE,MAAM,EAAE,CAAC,GAAI,OAAO,EAAE,MAAM,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAEO,SAAS,qBACd,MACA,YACoB;AACpB,QAAM,MAA0B,CAAC;AACjC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAO,CAAC,MAAc,SAAuB;AACjD,UAAM,MAAM,GAAG,IAAI,IAAI,IAAI;AAC3B,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,UAAM,OAAO,OAAO,KAAK,SAAS,IAAI;AACtC,QAAI,KAAK;AAAA,MACP,SAASC,SAAQ,MAAM,IAAI;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,UAAU;AAAA;AAAA;AAAA;AAAA,MAIV,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,MAAMC,OAAK,SAAS,YAAY,KAAK,IAAI;AAAA,QACzC;AAAA,QACA,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,UAAU,KAAK,SAAS,CAAC,YAAY,oBAAoB,oBAAoB,qBAAqB,CAAC,GAAG;AACxG,eAAW,EAAE,KAAK,KAAKF,SAAQ,cAAc,KAAK,OAAO,EAAG,MAAK,aAAa,IAAI;AAAA,EACpF;AACA,MACE,UAAU,KAAK,SAAS;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,GACD;AACA,eAAW,EAAE,KAAK,KAAKA,SAAQ,iBAAiB,KAAK,OAAO,EAAG,MAAK,kBAAkB,IAAI;AAAA,EAC5F;AACA,SAAO;AACT;;;ACxEA,OAAOG,YAAU;AACjB,SAAS,WAAAC,gBAAe;AAuBxB,IAAM,iBAAiB;AACvB,IAAM,oBACJ;AACF,IAAM,iBACJ;AAEF,SAAS,gBAAgB,OAAoC;AAC3D,MAAI,CAAC,MAAO,QAAO;AAGnB,MAAI,KAAK,KAAK,KAAK,KAAK,MAAM,WAAW,GAAG,EAAG,QAAO;AACtD,SAAO,YAAY,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;AACtD;AAEA,SAAS,kBAAkB,GAAmB;AAC5C,SAAO,EAAE,YAAY,EAAE,QAAQ,cAAc,EAAE;AACjD;AAOA,SAAS,YAAY,SAAgC;AACnD,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,oBAAkB,YAAY;AAC9B,MAAI;AACJ,UAAQ,IAAI,kBAAkB,KAAK,OAAO,OAAO,MAAM;AACrD,UAAM,MAAM,EAAE,CAAC;AACf,mBAAe,IAAI,kBAAkB,GAAG,GAAG,GAAG;AAAA,EAChD;AACA,SAAO;AAAA,IACL;AAAA,IACA,eAAe,eAAe,KAAK,OAAO;AAAA,EAC5C;AACF;AAEA,SAAS,eACP,QACA,KACyB;AACzB,QAAM,MAAM,kBAAkB,MAAM;AACpC,QAAM,SAAS,IAAI,eAAe,IAAI,GAAG;AACzC,MAAI,OAAQ,QAAO,EAAE,MAAM,OAAO,MAAM,GAAG;AAC3C,MAAI,IAAI,cAAe,QAAO,EAAE,MAAM,eAAe;AAOrD,SAAO;AACT;AAEO,SAAS,sBACd,MACA,YACoB;AACpB,QAAM,MAA0B,CAAC;AACjC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAM,YAAY,KAAK,OAAO;AACpC,iBAAe,YAAY;AAC3B,MAAI;AACJ,UAAQ,IAAI,eAAe,KAAK,KAAK,OAAO,OAAO,MAAM;AACvD,UAAM,SAAS,EAAE,CAAC;AAClB,UAAM,OAAO,EAAE,CAAC,GAAG,KAAK;AACxB,UAAM,OAAO,gBAAgB,IAAI,IAAI,OAAQ;AAC7C,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,UAAM,aAAa,eAAe,QAAQ,GAAG;AAC7C,QAAI,CAAC,WAAY;AACjB,SAAK,IAAI,IAAI;AACb,UAAM,EAAE,KAAK,IAAI;AACjB,UAAM,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC,CAAC;AACtC,QAAI,KAAK;AAAA,MACP,SAASC,SAAQ,MAAM,IAAI;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,UAAU;AAAA;AAAA;AAAA;AAAA,MAIV,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,MAAMC,OAAK,SAAS,YAAY,KAAK,IAAI;AAAA,QACzC;AAAA,QACA,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AClHA,OAAOC,YAAU;AACjB,SAAS,WAAAC,gBAAe;AA2BxB,IAAM,wBACJ;AACF,IAAM,yBACJ;AAMF,IAAM,qBACJ;AAOF,SAAS,gBAAgB,SAA4C;AACnE,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,IAAI,4BAA4B,KAAK,QAAQ,KAAK,CAAC;AACzD,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,OAAO,EAAE,CAAC;AAChB,MAAI,CAAC,wBAAwB,KAAK,IAAI,EAAG,QAAO;AAChD,SAAO;AACT;AAOA,SAASC,aAAY,SAAgC;AACnD,SAAO;AAAA,IACL,eAAe,sBAAsB,KAAK,OAAO;AAAA,IACjD,gBAAgB,uBAAuB,KAAK,OAAO;AAAA,EACrD;AACF;AAMA,SAAS,yBAAyB,MAAc,KAA6B;AAC3E,MAAI,SAAS,eAAgB,QAAO,IAAI;AACxC,SAAO,IAAI;AACb;AAEO,SAAS,0BACd,MACA,YACoB;AACpB,QAAM,MAAMA,aAAY,KAAK,OAAO;AACpC,MAAI,CAAC,IAAI,iBAAiB,CAAC,IAAI,eAAgB,QAAO,CAAC;AAEvD,QAAM,MAA0B,CAAC;AACjC,QAAM,OAAO,oBAAI,IAAY;AAC7B,qBAAmB,YAAY;AAC/B,MAAI;AACJ,UAAQ,IAAI,mBAAmB,KAAK,KAAK,OAAO,OAAO,MAAM;AAC3D,UAAM,OAAO,EAAE,CAAC;AAChB,QAAI,CAAC,yBAAyB,MAAM,GAAG,EAAG;AAQ1C,UAAM,OAAO,gBAAgB,EAAE,CAAC,CAAC;AACjC,UAAM,OAAO,QAAQ;AACrB,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,SAAK,IAAI,IAAI;AAEb,UAAM,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC,CAAC;AACtC,QAAI,KAAK;AAAA,MACP,SAASC,SAAQ,YAAY,IAAI;AAAA,MACjC;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,MAKV,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,MAAMC,OAAK,SAAS,YAAY,KAAK,IAAI;AAAA,QACzC;AAAA,QACA,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AP1FA,SAAS,qBAAqB,IAAgE;AAC5F,UAAQ,GAAG,UAAU;AAAA,IACnB,KAAK;AACH,aAAOC,WAAS;AAAA,IAClB,KAAK;AACH,aAAOA,WAAS;AAAA,IAClB;AACE,aAAOA,WAAS;AAAA,EACpB;AACF;AAEA,SAAS,UAAU,MAAuB;AACxC,SACE,KAAK,WAAW,MAAM,KACtB,KAAK,WAAW,IAAI,KACpB,KAAK,WAAW,UAAU;AAE9B;AAEA,eAAe,yBACb,OACA,UAC4B;AAC5B,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG;AAC/C,UAAM,YAAgC,CAAC;AACvC,eAAW,QAAQ,OAAO;AAIxB,UAAI,WAAW,KAAK,IAAI,EAAG;AAM3B,YAAM,SAAS,qBAAqB,KAAK,OAAO;AAChD,YAAM,aAAa,EAAE,MAAM,KAAK,MAAM,SAAS,OAAO;AACtD,gBAAU,KAAK,GAAG,uBAAuB,YAAY,QAAQ,GAAG,CAAC;AACjE,gBAAU,KAAK,GAAG,uBAAuB,YAAY,QAAQ,GAAG,CAAC;AACjE,gBAAU,KAAK,GAAG,qBAAqB,YAAY,QAAQ,GAAG,CAAC;AAC/D,gBAAU,KAAK,GAAG,sBAAsB,YAAY,QAAQ,GAAG,CAAC;AAChE,gBAAU,KAAK,GAAG,0BAA0B,YAAY,QAAQ,GAAG,CAAC;AAAA,IACtE;AACA,QAAI,UAAU,WAAW,EAAG;AAE5B,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,MAAM,WAAW;AAC1B,UAAI,CAAC,MAAM,QAAQ,GAAG,OAAO,GAAG;AAC9B,cAAM,OAAkB;AAAA,UACtB,IAAI,GAAG;AAAA,UACP,MAAMC,WAAS;AAAA,UACf,MAAM,GAAG;AAAA;AAAA;AAAA;AAAA,UAIT,UAAU,UAAU,GAAG,IAAI,IAAI,QAAQ;AAAA,UACvC,MAAM,GAAG;AAAA,QACX;AACA,cAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,MACF;AAEA,YAAM,WAAW,qBAAqB,EAAE;AACxC,YAAM,aAAaC,wBAAuB,GAAG,cAAc;AAO3D,YAAM,UAAUC,SAAQ,GAAG,SAAS,IAAI;AACxC,YAAM,EAAE,YAAY,YAAY,GAAG,YAAY,EAAE,IAAI;AAAA,QACnD;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb;AAAA,MACF;AACA,oBAAc;AACd,oBAAc;AAId,UAAI,CAACC,sBAAqB,UAAU,GAAG;AACrC,6BAAqB;AAAA,UACnB,QAAQ;AAAA,UACR,QAAQ,GAAG;AAAA,UACX,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB,GAAG;AAAA,UACnB,UAAU,GAAG;AAAA,QACf,CAAC;AACD;AAAA,MACF;AACA,YAAM,SAASC,iBAAW,YAAY,GAAG,SAAS,QAAQ;AAC1D,UAAI,UAAU,IAAI,MAAM,EAAG;AAC3B,gBAAU,IAAI,MAAM;AACpB,UAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,cAAM,OAAkB;AAAA,UACtB,IAAI;AAAA,UACJ,QAAQ;AAAA,UACR,QAAQ,GAAG;AAAA,UACX,MAAM;AAAA,UACN,YAAYC,aAAW;AAAA,UACvB;AAAA,UACA,UAAU,GAAG;AAAA,QACf;AACA,cAAM,eAAe,QAAQ,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,WAAW;AAClC;AAEA,eAAsB,aACpB,OACA,UAC4B;AAC5B,QAAM,OAAO,MAAM,iBAAiB,OAAO,QAAQ;AACnD,QAAM,MAAM,MAAM,yBAAyB,OAAO,QAAQ;AAI1D,QAAM,SAAS,MAAM,kBAAkB,OAAO,QAAQ;AACtD,SAAO;AAAA,IACL,YAAY,KAAK,aAAa,IAAI,aAAa,OAAO;AAAA,IACtD,YAAY,KAAK,aAAa,IAAI,aAAa,OAAO;AAAA,EACxD;AACF;;;AQlKA,OAAOC,YAAU;AAEjB,SAAS,YAAAC,YAAU,cAAAC,cAAY,0BAAAC,gCAA8B;;;ACD7D,SAAS,YAAAC,YAAU,WAAAC,gBAAe;AAI3B,SAAS,cACd,MACA,MACA,WAAW,QACX,QACW;AACX,SAAO;AAAA,IACL,IAAIA,SAAQ,MAAM,IAAI;AAAA,IACtB,MAAMD,WAAS;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,EACpD;AACF;AAKO,SAAS,cAAc,OAAuB;AACnD,QAAM,QAAQ,MAAM,YAAY;AAChC,QAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AACtC,MAAI,KAAK,WAAW,UAAU,EAAG,QAAO;AACxC,MAAI,KAAK,WAAW,OAAO,KAAK,KAAK,WAAW,SAAS,EAAG,QAAO;AACnE,MAAI,KAAK,WAAW,OAAO,EAAG,QAAO;AACrC,MAAI,KAAK,WAAW,OAAO,EAAG,QAAO;AACrC,MAAI,KAAK,WAAW,UAAU,EAAG,QAAO;AACxC,MAAI,KAAK,WAAW,OAAO,KAAK,KAAK,SAAS,OAAO,EAAG,QAAO;AAC/D,MAAI,KAAK,WAAW,WAAW,EAAG,QAAO;AACzC,SAAO;AACT;;;ADlBA,SAAS,cAAc,OAA+C;AACpE,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,OAAO,KAAK,KAAK;AAC1B;AAEA,SAAS,yBACP,MACA,UACe;AACf,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,KAAK,SAAS,QAAQE,OAAK,SAAS,EAAE,GAAG,MAAM,KAAM,QAAO,EAAE,KAAK;AAAA,EAC3E;AACA,SAAO;AACT;AAQA,eAAsB,gBACpB,OACA,UACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,MAAI,cAA6B;AACjC,aAAW,QAAQ,CAAC,sBAAsB,qBAAqB,GAAG;AAChE,UAAM,MAAMA,OAAK,KAAK,UAAU,IAAI;AACpC,QAAI,MAAM,OAAO,GAAG,GAAG;AACrB,oBAAc;AACd;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,YAAa,QAAO,EAAE,YAAY,WAAW;AAElD,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,SAAsB,WAAW;AAAA,EACnD,SAAS,KAAK;AACZ;AAAA,MACE;AAAA,MACAA,OAAK,SAAS,UAAU,WAAW;AAAA,MACnC;AAAA,IACF;AACA,WAAO,EAAE,YAAY,WAAW;AAAA,EAClC;AACA,MAAI,CAAC,SAAS,SAAU,QAAO,EAAE,YAAY,WAAW;AACxD,QAAM,eAAeA,OAAK,SAAS,UAAU,WAAW,EAAE,MAAMA,OAAK,GAAG,EAAE,KAAK,GAAG;AAElF,QAAM,sBAAsB,oBAAI,IAAoB;AACpD,aAAW,CAAC,aAAa,GAAG,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AACjE,UAAM,mBAAmB,yBAAyB,aAAa,QAAQ;AACvE,QAAI,kBAAkB;AACpB,0BAAoB,IAAI,aAAa,gBAAgB;AACrD;AAAA,IACF;AACA,UAAM,OAAO,IAAI,QAAQ,cAAc,IAAI,KAAK,IAAI;AACpD,UAAM,OAAO,cAAc,MAAM,WAAW;AAC5C,QAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,YAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,IACF;AACA,wBAAoB,IAAI,aAAa,KAAK,EAAE;AAAA,EAC9C;AAEA,aAAW,CAAC,aAAa,GAAG,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AACjE,UAAM,WAAW,oBAAoB,IAAI,WAAW;AACpD,QAAI,CAAC,SAAU;AACf,eAAW,OAAO,cAAc,IAAI,UAAU,GAAG;AAC/C,YAAM,WAAW,oBAAoB,IAAI,GAAG;AAC5C,UAAI,CAAC,SAAU;AACf,YAAM,SAASC,iBAAW,UAAU,UAAUC,WAAS,UAAU;AACjE,UAAI,MAAM,QAAQ,MAAM,EAAG;AAG3B,YAAM,OAAkB;AAAA,QACtB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAMA,WAAS;AAAA,QACf,YAAYC,aAAW;AAAA,QACvB,YAAYC,yBAAuB,YAAY;AAAA,QAC/C,UAAU,EAAE,MAAM,aAAa;AAAA,MACjC;AACA,YAAM,eAAe,QAAQ,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC3D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AEjHA,OAAOC,YAAU;AACjB,SAAS,YAAYC,YAAU;AAE/B,SAAS,YAAAC,YAAU,cAAAC,cAAY,0BAAAC,gCAA8B;AA2B7D,SAAS,eAAe,SAAkC;AACxD,MAAI,QAAuB;AAC3B,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAqB;AACzB,MAAI,aAA4B;AAChC,aAAW,OAAO,QAAQ,MAAM,IAAI,GAAG;AACrC,UAAM,OAAO,IAAI,KAAK;AACtB,QAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,EAAG;AACnC,QAAI,YAAY,KAAK,IAAI,GAAG;AAC1B,YAAM,YAAY,KAAK,MAAM,KAAK,EAAE,CAAC;AACrC,UAAI,aAAa,UAAU,YAAY,MAAM,UAAW,SAAQ;AAAA,IAClE,WAAW,cAAc,KAAK,IAAI,GAAG;AACnC,iBAAW,SAAS,KAAK,MAAM,KAAK,EAAE,MAAM,CAAC,GAAG;AAC9C,cAAM,OAAO,OAAO,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,GAAI,EAAE;AACrD,YAAI,OAAO,UAAU,IAAI,KAAK,CAAC,MAAM,SAAS,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,MACtE;AAAA,IACF,WAAW,kBAAkB,KAAK,IAAI,GAAG;AACvC,mBAAa;AAAA,IACf,WAAW,WAAW,KAAK,IAAI,GAAG;AAChC,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO,EAAE,OAAO,OAAO,YAAY,cAAc,IAAI;AACvD;AAKA,eAAsB,sBACpB,OACA,UACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,iBAAiBC,OAAK,KAAK,QAAQ,KAAK,YAAY;AAC1D,QAAI,CAAE,MAAM,OAAO,cAAc,EAAI;AACrC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMC,KAAG,SAAS,gBAAgB,MAAM;AAAA,IACpD,SAAS,KAAK;AACZ;AAAA,QACE;AAAA,QACAD,OAAK,SAAS,UAAU,cAAc;AAAA,QACtC;AAAA,MACF;AACA;AAAA,IACF;AACA,UAAM,QAAQ,eAAe,OAAO;AACpC,QAAI,CAAC,MAAM,MAAO;AAElB,UAAM,OAAO,cAAc,mBAAmB,MAAM,KAAK;AACzD,QAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,YAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,IACF;AAOA,UAAM,gBAAgBE,SAAQF,OAAK,SAAS,QAAQ,KAAK,cAAc,CAAC;AACxE,UAAM,eAAeE,SAAQF,OAAK,SAAS,UAAU,cAAc,CAAC;AACpE,UAAM,EAAE,YAAY,YAAY,IAAI,YAAY,GAAG,IAAI;AAAA,MACrD;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb;AAAA,IACF;AACA,kBAAc;AACd,kBAAc;AAKd,UAAM,SAASG,iBAAW,YAAY,KAAK,IAAIC,WAAS,OAAO;AAC/D,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,OAAkB;AAAA,QACtB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,MAAMA,WAAS;AAAA,QACf,YAAYC,aAAW;AAAA,QACvB,YAAYC,yBAAuB,YAAY;AAAA,QAC/C,UAAU;AAAA,UACR,MAAM;AAAA,UACN,GAAI,MAAM,aAAa,EAAE,SAAS,MAAM,WAAW,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC;AAAA,QACxE;AAAA,MACF;AACA,YAAM,eAAe,QAAQ,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC3D;AAAA,IACF;AAMA,eAAW,QAAQ,MAAM,OAAO;AAC9B,YAAM,WAAW,cAAc,QAAQ,OAAO,IAAI,CAAC;AACnD,UAAI,CAAC,MAAM,QAAQ,SAAS,EAAE,GAAG;AAC/B,cAAM,QAAQ,SAAS,IAAI,QAAQ;AACnC;AAAA,MACF;AACA,YAAM,aAAaH,iBAAW,YAAY,SAAS,IAAIC,WAAS,WAAW;AAC3E,UAAI,MAAM,QAAQ,UAAU,EAAG;AAC/B,YAAM,WAAsB;AAAA,QAC1B,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,QAAQ,SAAS;AAAA,QACjB,MAAMA,WAAS;AAAA,QACf,YAAYC,aAAW;AAAA,QACvB,YAAYC,yBAAuB,YAAY;AAAA,QAC/C,UAAU,EAAE,MAAM,cAAc,SAAS,UAAU,IAAI,GAAG;AAAA,MAC5D;AACA,YAAM,eAAe,YAAY,SAAS,QAAQ,SAAS,QAAQ,QAAQ;AAC3E;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,WAAW;AAClC;;;ACzJA,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AAEjB,SAAS,YAAAC,YAAU,cAAAC,cAAY,0BAAAC,gCAA8B;AAc7D,IAAM,cAAc;AAGpB,IAAM,eAAe;AAUrB,eAAe,YAAY,OAAe,QAAQ,GAAG,MAAM,GAAsB;AAC/E,MAAI,QAAQ,IAAK,QAAO,CAAC;AACzB,QAAM,MAAgB,CAAC;AACvB,QAAM,UAAU,MAAMC,KAAG,QAAQ,OAAO,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC/E,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,aAAa,IAAI,MAAM,IAAI,KAAK,MAAM,SAAS,aAAc;AACjE,YAAM,QAAQC,OAAK,KAAK,OAAO,MAAM,IAAI;AACzC,UAAI,MAAM,gBAAgB,KAAK,EAAG;AAClC,UAAI,KAAK,GAAI,MAAM,YAAY,OAAO,QAAQ,GAAG,GAAG,CAAE;AAAA,IACxD,WAAW,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,KAAK,GAAG;AACvD,UAAI,KAAKA,OAAK,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,UAAU,SAAiB,MAAuD;AACzF,QAAM,OAAO,QAAQ,QAAQ,KAAK,IAAI;AACtC,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,QAAQ;AACZ,WAAS,IAAI,MAAM,IAAI,QAAQ,QAAQ,KAAK;AAC1C,UAAM,KAAK,QAAQ,CAAC;AACpB,QAAI,OAAO,IAAK;AAAA,aACP,OAAO,KAAK;AACnB;AACA,UAAI,UAAU,EAAG,QAAO,EAAE,MAAM,QAAQ,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,OAAO,EAAE;AAAA,IAC/E;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAASC,QAAO,SAAiB,OAAuB;AACtD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,SAAS,IAAI,QAAQ,QAAQ,KAAK;AACpD,QAAI,QAAQ,CAAC,MAAM,KAAM;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,eAAsB,sBACpB,OACA,UACqD;AACrD,MAAI,aAAa;AACjB,MAAI,aAAa;AACjB,QAAM,QAAQ,MAAM,YAAY,QAAQ;AACxC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,MAAMF,KAAG,SAAS,MAAM,MAAM;AAC9C,UAAM,eAAeG,SAAQF,OAAK,SAAS,UAAU,IAAI,CAAC;AAI1D,UAAM,YAA0B,CAAC;AACjC,UAAM,QAAQ,oBAAI,IAAwB;AAC1C,gBAAY,YAAY;AACxB,QAAI;AACJ,YAAQ,IAAI,YAAY,KAAK,OAAO,OAAO,MAAM;AAC/C,YAAM,OAAO,EAAE,CAAC;AAChB,YAAM,OAAO,EAAE,CAAC;AAChB,YAAM,OAAO,cAAc,MAAM,MAAM,KAAK;AAC5C,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,cAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,MACF;AACA,YAAM,OAAO,UAAU,SAAS,YAAY,SAAS;AACrD,YAAM,WAAuB;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,MAAM,MAAM,QAAQ;AAAA,QACpB,YAAY,MAAM,UAAU,YAAY;AAAA,MAC1C;AACA,gBAAU,KAAK,QAAQ;AACvB,YAAM,IAAI,GAAG,IAAI,IAAI,IAAI,IAAI,QAAQ;AAAA,IACvC;AAMA,eAAW,YAAY,WAAW;AAChC,YAAM,OAAO,oBAAI,IAAY;AAC7B,mBAAa,YAAY;AACzB,UAAI;AACJ,cAAQ,MAAM,aAAa,KAAK,SAAS,IAAI,OAAO,MAAM;AACxD,cAAM,MAAM,GAAG,IAAI,CAAC,CAAE,IAAI,IAAI,CAAC,CAAE;AACjC,YAAI,QAAQ,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,GAAI;AACjD,cAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,YAAI,CAAC,OAAQ;AACb,YAAI,KAAK,IAAI,OAAO,MAAM,EAAG;AAC7B,aAAK,IAAI,OAAO,MAAM;AACtB,cAAM,SAASG,iBAAW,SAAS,QAAQ,OAAO,QAAQC,WAAS,UAAU;AAC7E,YAAI,MAAM,QAAQ,MAAM,EAAG;AAC3B,cAAM,OAAOH,QAAO,SAAS,SAAS,aAAa,IAAI,KAAK;AAC5D,cAAM,OAAkB;AAAA,UACtB,IAAI;AAAA,UACJ,QAAQ,SAAS;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf,MAAMG,WAAS;AAAA,UACf,YAAYC,aAAW;AAAA,UACvB,YAAYC,yBAAuB,YAAY;AAAA,UAC/C,UAAU,EAAE,MAAM,cAAc,MAAM,SAAS,IAAI;AAAA,QACrD;AACA,cAAM,eAAe,QAAQ,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,WAAW;AAClC;;;AChJA,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AACjB,SAAS,qBAAAC,0BAAyB;AAUlC,IAAM,yBAAiD;AAAA,EACrD,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,WAAW;AAAA,EACX,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AACX;AAEA,eAAeC,eAAc,OAAe,QAAQ,GAAG,MAAM,GAAsB;AACjF,MAAI,QAAQ,IAAK,QAAO,CAAC;AACzB,QAAM,MAAgB,CAAC;AACvB,QAAM,UAAU,MAAMC,KAAG,QAAQ,OAAO,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC/E,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,YAAM,QAAQC,OAAK,KAAK,OAAO,MAAM,IAAI;AACzC,UAAI,MAAM,gBAAgB,KAAK,EAAG;AAClC,UAAI,KAAK,GAAI,MAAMF,eAAc,OAAO,QAAQ,GAAG,GAAG,CAAE;AAAA,IAC1D,WAAW,MAAM,OAAO,KAAK,uBAAuB,IAAIE,OAAK,QAAQ,MAAM,IAAI,CAAC,GAAG;AACjF,UAAI,KAAKA,OAAK,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAMA,eAAsB,gBACpB,OACA,UACqD;AACrD,MAAI,aAAa;AACjB,QAAM,QAAQ,MAAMF,eAAc,QAAQ;AAC1C,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,MAAMC,KAAG,SAAS,MAAM,MAAM;AAC9C,QAAI;AACJ,QAAI;AACF,aAAOE,mBAAkB,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAW;AAAA,IACnE,QAAQ;AACN;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,UAAU,KAAM;AACvC,YAAM,YAAY,uBAAuB,IAAI,IAAI;AACjD,UAAI,CAAC,UAAW;AAChB,YAAM,aAAa,IAAI,SAAS,YAC5B,GAAG,IAAI,SAAS,SAAS,IAAI,IAAI,SAAS,IAAI,KAC9C,IAAI,SAAS;AACjB,YAAM,OAAO,cAAc,WAAW,YAAY,YAAY;AAC9D,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,GAAG;AAC3B,cAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,YAAY,EAAE;AACrC;;;ACzDA,eAAsB,SACpB,OACA,UACA,UAC6B;AAC7B,QAAM,UAAU,MAAM,gBAAgB,OAAO,UAAU,QAAQ;AAC/D,QAAM,aAAa,MAAM,sBAAsB,OAAO,UAAU,QAAQ;AACxE,QAAM,YAAY,MAAM,sBAAsB,OAAO,QAAQ;AAC7D,QAAM,MAAM,MAAM,gBAAgB,OAAO,QAAQ;AAEjD,SAAO;AAAA,IACL,YACE,QAAQ,aAAa,WAAW,aAAa,UAAU,aAAa,IAAI;AAAA,IAC1E,YACE,QAAQ,aAAa,WAAW,aAAa,UAAU,aAAa,IAAI;AAAA,EAC5E;AACF;;;ACQA,OAAOC,YAAU;;;ACvCjB,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;AAEjB,SAAS,YAAAC,YAAU,cAAAC,oBAAkB;AAQrC,SAAS,sBAAsB,OAA0B;AACvD,QAAM,UAAoB,CAAC;AAC3B,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,QAAK,MAAoB,SAASD,WAAS,SAAU;AACrD,QAAI,MAAM,aAAa,EAAE,EAAE,WAAW,KAAK,MAAM,cAAc,EAAE,EAAE,WAAW,GAAG;AAC/E,cAAQ,KAAK,EAAE;AAAA,IACjB;AAAA,EACF,CAAC;AACD,aAAW,MAAM,QAAS,OAAM,SAAS,EAAE;AAC3C,SAAO,QAAQ;AACjB;AAUO,SAAS,kBAAkB,OAAkB,MAAsB;AACxE,QAAM,aAAa,KAAK,MAAM,IAAI,EAAE,KAAK,GAAG;AAC5C,QAAM,SAAmB,CAAC;AAC1B,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,OAAO;AACb,QAAI,KAAK,eAAeC,aAAW,UAAW;AAC9C,QAAI,CAAC,KAAK,UAAU,KAAM;AAC1B,QAAI,KAAK,SAAS,SAAS,WAAY,QAAO,KAAK,EAAE;AAAA,EACvD,CAAC;AACD,aAAW,MAAM,OAAQ,OAAM,SAAS,EAAE;AAC1C,wBAAsB,KAAK;AAC3B,SAAO,OAAO;AAChB;AAkBO,SAAS,kCACd,OACA,UACA,cAAiC,CAAC,GAC1B;AACR,QAAM,SAAmB,CAAC;AAC1B,QAAM,QAAQ,CAAC,UAAU,GAAG,WAAW;AACvC,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,OAAO;AACb,QAAI,KAAK,eAAeA,aAAW,UAAW;AAC9C,UAAM,eAAe,KAAK,UAAU;AACpC,QAAI,CAAC,aAAc;AACnB,QAAIF,OAAK,WAAW,YAAY,GAAG;AACjC,UAAI,CAACD,YAAW,YAAY,EAAG,QAAO,KAAK,EAAE;AAC7C;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,KAAK,CAAC,SAASA,YAAWC,OAAK,KAAK,MAAM,YAAY,CAAC,CAAC;AAC5E,QAAI,CAAC,MAAO,QAAO,KAAK,EAAE;AAAA,EAC5B,CAAC;AACD,aAAW,MAAM,OAAQ,OAAM,SAAS,EAAE;AAC1C,wBAAsB,KAAK;AAC3B,SAAO,OAAO;AAChB;;;ADHA,eAAsB,qBACpB,OACA,UACA,OAAuB,CAAC,GACA;AACxB,QAAM,mBAAmB;AAIzB,wBAAsB;AAEtB,QAAM,WAAW,MAAM,iBAAiB,QAAQ;AAEhD,QAAM,cAAc,gBAAgB,OAAO,QAAQ;AACnD,QAAM,kBAAkB,OAAO,UAAU,QAAQ;AACjD,QAAM,WAAW,MAAM,SAAS,OAAO,QAAQ;AAC/C,QAAM,cAAc,MAAM,WAAW,OAAO,QAAQ;AACpD,QAAM,SAAS,MAAM,sBAAsB,OAAO,UAAU,QAAQ;AACpE,QAAM,SAAS,MAAM,eAAe,OAAO,UAAU,QAAQ;AAG7D,QAAM,aAAa,MAAM,UAAU,OAAO,QAAQ;AAKlD,QAAM,YAAY,MAAM,eAAe,OAAO,QAAQ;AACtD,QAAM,SAAS,MAAM,aAAa,OAAO,QAAQ;AACjD,QAAM,SAAS,MAAM,SAAS,OAAO,UAAU,QAAQ;AAOvD,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA,SAAS,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,EAC3B;AACA,QAAM,oBAAoB,qBAAqB,KAAK;AAKpD,MAAI,KAAK,gBAAiB,OAAM,KAAK,gBAAgB,KAAK;AAK1D,QAAM,eAAe,sBAAsB;AAC3C,MAAI,KAAK,cAAc,aAAa,SAAS,GAAG;AAC9C,QAAI;AACF,YAAM,sBAAsB,cAAc,KAAK,UAAU;AAAA,IAC3D,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,+CAA+C,KAAK,UAAU,KAAM,IAAc,OAAO;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAKA,QAAM,iBAAiB,sBAAsB;AAC7C,MACE,qBAAqB,KACrB,KAAK,cACL,eAAe,SAAS,GACxB;AAGA,UAAM,eAAeG,OAAK,KAAKA,OAAK,QAAQ,KAAK,UAAU,GAAG,iBAAiB;AAC/E,QAAI;AACF,YAAM,uBAAuB,gBAAgB,YAAY;AAAA,IAC3D,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,sDAAsD,YAAY,KAAM,IAAc,OAAO;AAAA,MAC/F;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAwB;AAAA,IAC5B,YACE,cACA,SAAS,aACT,YAAY,aACZ,OAAO,aACP,OAAO,aACP,WAAW,aACX,UAAU,aACV,OAAO,aACP,OAAO;AAAA,IACT,YACE,SAAS,aACT,YAAY,aACZ,OAAO,aACP,OAAO,aACP,WAAW,aACX,UAAU,aACV,OAAO,aACP,OAAO;AAAA,IACT;AAAA,IACA,kBAAkB,aAAa;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,kBAAkB,eAAe;AAAA,IACjC;AAAA,EACF;AAKA,gBAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS,KAAK,WAAW;AAAA,IACzB,SAAS;AAAA,MACP,SAAS,KAAK,WAAW;AAAA,MACzB,WAAW,SAAS;AAAA,MACpB,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO;AAAA,IACrB;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AExLA;AAAA,EACE,cAAAC;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,OACK;AAiCP,SAAS,UAAU,QAAgB,QAAgB,MAAsB;AACvE,SAAO,GAAG,IAAI,IAAI,MAAM,IAAI,MAAM;AACpC;AAEA,SAAS,YAAY,OAA2C;AAC9D,QAAM,UAAU,oBAAI,IAAwB;AAC5C,QAAM,YAAY,CAAC,IAAI,UAAU;AAC/B,UAAM,IAAI;AACV,UAAM,SAAS,YAAY,EAAE;AAG7B,UAAM,aAAa,QAAQ,cAAc,EAAE;AAC3C,UAAM,MAAM,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI;AAChD,UAAM,MACJ,QAAQ,IAAI,GAAG,KAAK,EAAE,QAAQ,EAAE,QAAQ,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK;AACzE,YAAQ,YAAY;AAAA,MAClB,KAAKC,aAAW;AACd,YAAI,YAAY;AAChB;AAAA,MACF,KAAKA,aAAW;AACd,YAAI,WAAW;AACf;AAAA,MACF,KAAKA,aAAW;AACd,YAAI,WAAW;AACf;AAAA,MACF;AAGE,YAAI,EAAE,eAAeA,aAAW,MAAO,KAAI,QAAQ;AAAA,IACvD;AACA,YAAQ,IAAI,KAAK,GAAG;AAAA,EACtB,CAAC;AACD,SAAO;AACT;AAEA,SAAS,eAAe,OAAkB,QAAyB;AACjE,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,SAAO,MAAM,SAASC,WAAS;AACjC;AAUA,SAAS,uBAAuB,OAAkB,QAAyB;AACzE,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,SAAO,MAAM,SAASA,WAAS;AACjC;AAEA,SAAS,gBAAgB,GAAmB;AAC1C,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACnC;AAEA,SAAS,yBAAyB,QAAgB,QAAgB,MAAsB;AACtF,SAAO,iBAAiB,MAAM,WAAM,MAAM,KAAK,IAAI;AACrD;AAEA,SAAS,0BAA0B,QAAgB,QAAgB,MAAsB;AACvF,SAAO,uBAAuB,MAAM,WAAM,MAAM,KAAK,IAAI;AAC3D;AAEA,IAAM,kCACJ;AACF,IAAM,mCACJ;AACF,IAAM,+BACJ;AAaF,SAAS,iBAAiB,MAAyB;AACjD,MAAI,OAAO,KAAK,eAAe,SAAU,QAAO,gBAAgB,KAAK,UAAU;AAC/E,SAAO,gBAAgB,kBAAkB,IAAI,CAAC;AAChD;AAeA,IAAM,wBAAoD,oBAAI,IAAI;AAAA,EAChEC,WAAS;AAAA,EACTA,WAAS;AAAA,EACTA,WAAS;AAAA,EACTA,WAAS;AACX,CAAC;AAED,SAAS,yBACP,OACA,QACc;AACd,QAAM,MAAoB,CAAC;AAM3B,MAAI,OAAO,SAASA,WAAS,SAAU,QAAO;AAE9C,MAAI,OAAO,aAAa,CAAC,OAAO,YAAY,sBAAsB,IAAI,OAAO,IAAI,GAAG;AAKlF,QAAI,CAAC,eAAe,OAAO,OAAO,MAAM,GAAG;AAIzC,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,QACjB,WAAW,OAAO;AAAA,QAClB,YAAY,iBAAiB,OAAO,SAAS;AAAA,QAC7C,QAAQ,yBAAyB,OAAO,QAAQ,OAAO,QAAQ,OAAO,IAAI;AAAA,QAC1E,gBAAgB;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,OAAO,YAAY,CAAC,OAAO,aAAa,CAAC,uBAAuB,OAAO,OAAO,MAAM,GAAG;AAMzF,QAAI,KAAK;AAAA,MACP,MAAM;AAAA,MACN,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,YAAY,iBAAiB,OAAO,QAAQ;AAAA,MAC5C,QAAQ,0BAA0B,OAAO,QAAQ,OAAO,QAAQ,OAAO,IAAI;AAAA,MAC3E,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOA,SAAS,gBAAgB,KAAiC;AACxD,QAAM,MAAM,IAAI,oBAAoB,KAAK;AACzC,MAAI,CAAC,IAAK,QAAO;AAGjB,QAAM,QAAQ,IAAI,YAAY,GAAG;AACjC,MAAI,UAAU,GAAI,QAAO;AACzB,QAAM,OAAO,IAAI,MAAM,QAAQ,CAAC;AAChC,MAAI,QAAQ,KAAK,IAAI,EAAG,QAAO,IAAI,MAAM,GAAG,KAAK;AACjD,SAAO;AACT;AAEA,SAAS,yBAAyB,OAAkB,OAAwB;AAC1E,aAAW,UAAU,MAAM,cAAc,KAAK,GAAG;AAC/C,UAAM,IAAI,MAAM,kBAAkB,MAAM;AACxC,QAAI,EAAE,SAASA,WAAS,iBAAiB,EAAE,eAAeF,aAAW,WAAW;AAC9E,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBACP,OACA,OACA,KACc;AACd,QAAM,eAAe,gBAAgB,GAAG;AACxC,MAAI,CAAC,aAAc,QAAO,CAAC;AAC3B,MAAI,CAAC,yBAAyB,OAAO,KAAK,EAAG,QAAO,CAAC;AAErD,QAAM,MAAoB,CAAC;AAC3B,aAAW,UAAU,MAAM,cAAc,KAAK,GAAG;AAC/C,UAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,QAAI,KAAK,SAASE,WAAS,YAAa;AACxC,QAAI,KAAK,eAAeF,aAAW,SAAU;AAC7C,UAAM,SAAS,MAAM,kBAAkB,KAAK,MAAM;AAClD,QAAI,OAAO,SAASC,WAAS,aAAc;AAC3C,UAAM,eAAe,OAAO,MAAM,KAAK;AACvC,QAAI,CAAC,aAAc;AACnB,QAAI,iBAAiB,aAAc;AAEnC,QAAI,KAAK;AAAA,MACP,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,eAAe;AAAA,MACf;AAAA,MACA,YAAY,gBAAgB,kBAAkB,IAAI,CAAC;AAAA,MACnD,QAAQ,mBAAmB,KAAK,gBAAgB,YAAY,4BAA4B,YAAY;AAAA,MACpG,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,wBACP,OACA,OACA,KACc;AACd,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,IAAI,gBAAgB,CAAC;AAElC,aAAW,UAAU,MAAM,cAAc,KAAK,GAAG;AAC/C,UAAM,OAAO,MAAM,kBAAkB,MAAM;AAC3C,QAAI,KAAK,SAASC,WAAS,YAAa;AACxC,QAAI,KAAK,eAAeF,aAAW,SAAU;AAC7C,UAAM,SAAS,MAAM,kBAAkB,KAAK,MAAM;AAClD,QAAI,OAAO,SAASC,WAAS,aAAc;AAI3C,eAAW,QAAQ,YAAY,GAAG;AAChC,UAAI,KAAK,WAAW,OAAO,OAAQ;AACnC,YAAM,WAAW,KAAK,KAAK,MAAM;AACjC,UAAI,CAAC,SAAU;AACf,YAAM,SAAS;AAAA,QACb,KAAK;AAAA,QACL;AAAA,QACA,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AACA,UAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,YAAI,KAAK;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ,KAAK;AAAA,UACb,kBAAkB;AAAA,UAClB,iBAAiB,OAAO;AAAA,UACxB,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,QAAQ,OAAO;AAAA,UACf,gBAAgB,OAAO,mBACnB,WAAW,KAAK,MAAM,UAAU,OAAO,gBAAgB,MACvD,cAAc,KAAK,MAAM,wCAAwC,OAAO,MAAM,IAAI,OAAO,aAAa;AAAA,QAC5G,CAAC;AAAA,MACH;AAAA,IACF;AAMA,eAAW,QAAQ,eAAe,GAAG;AACnC,YAAM,WAAW,KAAK,KAAK,OAAO;AAClC,UAAI,CAAC,SAAU;AACf,YAAM,SAAS,mBAAmB,MAAM,QAAQ;AAChD,UAAI,CAAC,OAAO,cAAc,OAAO,QAAQ;AACvC,cAAM,UAAyB;AAAA,UAC7B,MAAM,KAAK,QAAQ;AAAA,UACnB,QAAQ,OAAO;AAAA,UACf,SAAS,KAAK;AAAA,QAChB;AACA,YAAI,KAAK;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ,KAAK;AAAA,UACb,MAAM;AAAA,UACN,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,QAAQ,OAAO;AAAA,UACf,gBAAgB,sBAAsB,KAAK,OAAO,IAAI,QAAQ;AAAA,QAChE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,GAAe,QAAyB;AAC5D,SAAO,EAAE,WAAW,UAAU,EAAE,WAAW;AAC7C;AAWA,SAAS,2BAA2B,KAAiC;AACnE,QAAM,eAAe,oBAAI,IAAY;AACrC,QAAM,eAAe,oBAAI,IAAY;AACrC,aAAW,KAAK,KAAK;AACnB,QAAI,EAAE,SAAS,gBAAiB;AAChC,iBAAa,IAAI,GAAG,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE;AAC3C,iBAAa,IAAIE,YAAW,EAAE,aAAa,CAAC;AAAA,EAC9C;AACA,MAAI,aAAa,SAAS,EAAG,QAAO;AACpC,SAAO,IAAI,OAAO,CAAC,MAAM;AACvB,QACE,EAAE,SAAS,uBACX,aAAa,IAAI,GAAG,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,GAC3C;AACA,aAAO;AAAA,IACT;AACA,QAAI,EAAE,SAAS,sBAAsB,aAAa,IAAI,EAAE,MAAM,EAAG,QAAO;AACxE,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,mBACd,OACA,OAA4B,CAAC,GACX;AAClB,QAAM,MAAoB,CAAC;AAG3B,QAAM,UAAU,YAAY,KAAK;AACjC,aAAW,UAAU,QAAQ,OAAO,GAAG;AACrC,eAAW,KAAK,yBAAyB,OAAO,MAAM,EAAG,KAAI,KAAK,CAAC;AAAA,EACrE;AAGA,QAAM,YAAY,CAAC,QAAQ,UAAU;AACnC,UAAM,IAAI;AACV,QAAI,EAAE,SAASF,WAAS,YAAa;AACrC,UAAM,MAAM;AACZ,eAAW,KAAK,mBAAmB,OAAO,QAAQ,GAAG,EAAG,KAAI,KAAK,CAAC;AAClE,eAAW,KAAK,wBAAwB,OAAO,QAAQ,GAAG,EAAG,KAAI,KAAK,CAAC;AAAA,EACzE,CAAC;AAID,QAAM,aAAa,2BAA2B,GAAG;AAIjD,MAAI,WAAW;AACf,MAAI,KAAK,MAAM;AACb,UAAM,UAAU,KAAK;AACrB,eAAW,SAAS,OAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,EACvD;AACA,MAAI,KAAK,kBAAkB,QAAW;AACpC,UAAM,YAAY,KAAK;AACvB,eAAW,SAAS,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS;AAAA,EAC7D;AACA,MAAI,KAAK,MAAM;AACb,UAAM,SAAS,KAAK;AACpB,eAAW,SAAS,OAAO,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AAAA,EAC3D;AAKA,QAAM,kBAAkD;AAAA,IACtD,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB,oBAAoB;AAAA,EACtB;AACA,WAAS,KAAK,CAAC,GAAG,MAAM;AACtB,QAAI,EAAE,eAAe,EAAE,WAAY,QAAO,EAAE,aAAa,EAAE;AAC3D,UAAM,OAAO,gBAAgB,EAAE,IAAI,IAAI,gBAAgB,EAAE,IAAI;AAC7D,QAAI,SAAS,EAAG,QAAO;AACvB,QAAI,EAAE,SAAS,EAAE,KAAM,QAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AACzD,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO,EAAE,OAAO,cAAc,EAAE,MAAM;AACjE,WAAO,EAAE,OAAO,cAAc,EAAE,MAAM;AAAA,EACxC,CAAC;AAED,SAAO,uBAAuB,MAAM;AAAA,IAClC,aAAa;AAAA,IACb,eAAe,SAAS;AAAA,IACxB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,CAAC;AACH;;;AC9cA,SAAS,YAAYG,YAAU;AAC/B,OAAOC,YAAU;AACjB,SAAS,cAAAC,cAAY,kBAAAC,uBAAsB;AAGpC,IAAM,iBAAiB;AAW9B,SAAS,cAAc,SAAyC;AAC9D,QAAM,QAAS,QAAQ,MACpB;AACH,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,cAAc,qBAAqB,KAAK,YAAY;AAC3D,eAAO,KAAK,WAAW;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,GAAG,SAAS,eAAe,EAAE;AACxC;AAqBA,SAAS,cAAc,SAAyC;AAC9D,SAAO,EAAE,GAAG,SAAS,eAAe,EAAE;AACxC;AAEA,SAAS,cAAc,SAAyC;AAC9D,QAAM,QAAS,QAAQ,MAKpB;AACH,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,KAAK;AAGnB,UAAI,CAAC,SAAS,MAAM,eAAe,WAAY;AAC/C,YAAM,aAAaD,aAAW;AAC9B,YAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAC3D,YAAM,SAAS,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;AACjE,YAAM,SAAS,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;AACjE,UAAI,QAAQ,UAAU,QAAQ;AAC5B,cAAM,QAAQC,gBAAe,QAAQ,QAAQ,IAAI;AACjD,cAAM,KAAK;AACX,YAAI,KAAK,IAAK,MAAK,MAAM;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,GAAG,SAAS,eAAe,EAAE;AACxC;AAEA,eAAe,UAAU,UAAiC;AACxD,QAAMH,KAAG,MAAMC,OAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D;AAEA,eAAsB,gBAAgB,OAAkB,SAAgC;AACtF,QAAM,UAAU,OAAO;AACvB,QAAM,UAA0B;AAAA,IAC9B,eAAe;AAAA,IACf,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,OAAO,MAAM,OAAO;AAAA,EACtB;AAGA,QAAM,MAAM,GAAG,OAAO;AACtB,QAAMD,KAAG,UAAU,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM;AACvD,QAAMA,KAAG,OAAO,KAAK,OAAO;AAC9B;AAEA,eAAsB,kBAAkB,OAAkB,SAAgC;AACxF,MAAI;AACJ,MAAI;AACF,UAAM,MAAMA,KAAG,SAAS,SAAS,MAAM;AAAA,EACzC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU;AACtD,UAAM;AAAA,EACR;AACA,MAAI,UAAU,KAAK,MAAM,GAAG;AAC5B,MAAI,QAAQ,kBAAkB,GAAG;AAC/B,cAAU,cAAc,OAAO;AAAA,EACjC;AACA,MAAI,QAAQ,kBAAkB,GAAG;AAC/B,cAAU,cAAc,OAAO;AAAA,EACjC;AACA,MAAI,QAAQ,kBAAkB,GAAG;AAC/B,cAAU,cAAc,OAAO;AAAA,EACjC;AACA,MAAI,QAAQ,kBAAkB,gBAAgB;AAC5C,UAAM,IAAI;AAAA,MACR,+CAA+C,QAAQ,aAAa,cAAc,cAAc;AAAA,IAClG;AAAA,EACF;AACA,QAAM,MAAM;AACZ,QAAM,OAAO,QAAQ,KAAK;AAC5B;AAyBO,SAAS,iBACd,OACA,SACA,OAA2B,CAAC,GAChB;AACZ,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,eAAe,KAAK,gBAAgB;AAC1C,MAAI,UAAU;AAEd,QAAM,OAAO,YAA2B;AACtC,QAAI,QAAS;AACb,QAAI;AACF,YAAM,gBAAgB,OAAO,OAAO;AAAA,IACtC,SAAS,KAAK;AACZ,cAAQ,MAAM,iCAAiC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,QAAM,WAAW,YAAY,MAAM;AACjC,SAAK,KAAK;AAAA,EACZ,GAAG,UAAU;AAEb,QAAM,WAAW,eACb,CAAC,WAAiC;AAChC,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,gBAAgB,OAAO,OAAO;AAAA,MACtC,SAAS,KAAK;AACZ,gBAAQ,MAAM,YAAY,MAAM,gBAAgB,GAAG;AAAA,MACrD,UAAE;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,GAAG;AAAA,EACL,IACA;AAEJ,MAAI,UAAU;AACZ,YAAQ,GAAG,WAAW,QAAQ;AAC9B,YAAQ,GAAG,UAAU,QAAQ;AAAA,EAC/B;AAEA,SAAO,MAAM;AACX,cAAU;AACV,kBAAc,QAAQ;AACtB,QAAI,UAAU;AACZ,cAAQ,IAAI,WAAW,QAAQ;AAC/B,cAAQ,IAAI,UAAU,QAAQ;AAAA,IAChC;AAAA,EACF;AACF;;;ACpMA,SAAS,YAAYI,YAAU;AAgD/B,eAAsB,oBAAoB,QAA4C;AACpF,MAAI,gBAAgB,KAAK,MAAM,GAAG;AAChC,UAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,SAAS,MAAM,YAAY,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,IAC3E;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACA,QAAM,MAAM,MAAMA,KAAG,SAAS,QAAQ,MAAM;AAC5C,SAAO,KAAK,MAAM,GAAG;AACvB;AAEA,SAAS,aACP,SACgB;AAChB,QAAM,IAAI,oBAAI,IAAe;AAC7B,MAAI,CAAC,QAAS,QAAO;AACrB,aAAW,SAAS,SAAS;AAC3B,UAAM,KAAM,MAAM,YAAY,MAA6B,MAAM;AACjE,QAAI,CAAC,GAAI;AACT,MAAE,IAAI,IAAI,MAAM,UAAe;AAAA,EACjC;AACA,SAAO;AACT;AAEO,SAAS,iBACd,WACA,cACA,qBAA4B,oBAAI,KAAK,GAAE,YAAY,GACxC;AACX,QAAM,YAAY,aAAwB,aAAa,OAAO,KAAK;AACnE,QAAM,YAAY,aAAwB,aAAa,OAAO,KAAK;AAEnE,QAAM,YAAY,oBAAI,IAAuB;AAC7C,YAAU,YAAY,CAAC,IAAI,UAAU,UAAU,IAAI,IAAI,KAAkB,CAAC;AAC1E,QAAM,YAAY,oBAAI,IAAuB;AAC7C,YAAU,YAAY,CAAC,IAAI,UAAU,UAAU,IAAI,IAAI,KAAkB,CAAC;AAE1E,QAAM,SAAoB;AAAA,IACxB,MAAM,EAAE,YAAY,aAAa,WAAW;AAAA,IAC5C,SAAS,EAAE,YAAY,kBAAkB;AAAA,IACzC,OAAO,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;AAAA,IAC9B,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;AAAA,IAChC,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;AAAA,EAClC;AAEA,aAAW,CAAC,IAAI,KAAK,KAAK,WAAW;AACnC,UAAM,SAAS,UAAU,IAAI,EAAE;AAC/B,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,MAAM,KAAK,KAAK;AAAA,IAC/B,WAAW,CAAC,aAAa,QAAQ,KAAK,GAAG;AACvC,aAAO,QAAQ,MAAM,KAAK,EAAE,IAAI,QAAQ,MAAM,CAAC;AAAA,IACjD;AAAA,EACF;AACA,aAAW,CAAC,IAAI,MAAM,KAAK,WAAW;AACpC,QAAI,CAAC,UAAU,IAAI,EAAE,EAAG,QAAO,QAAQ,MAAM,KAAK,MAAM;AAAA,EAC1D;AACA,aAAW,CAAC,IAAI,KAAK,KAAK,WAAW;AACnC,UAAM,SAAS,UAAU,IAAI,EAAE;AAC/B,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,MAAM,KAAK,KAAK;AAAA,IAC/B,WAAW,CAAC,aAAa,QAAQ,KAAK,GAAG;AACvC,aAAO,QAAQ,MAAM,KAAK,EAAE,IAAI,QAAQ,MAAM,CAAC;AAAA,IACjD;AAAA,EACF;AACA,aAAW,CAAC,IAAI,MAAM,KAAK,WAAW;AACpC,QAAI,CAAC,UAAU,IAAI,EAAE,EAAG,QAAO,QAAQ,MAAM,KAAK,MAAM;AAAA,EAC1D;AAEA,SAAO;AACT;AAIA,SAAS,aAAa,GAAY,GAAqB;AACrD,SAAO,cAAc,CAAC,MAAM,cAAc,CAAC;AAC7C;AAEA,SAAS,cAAc,OAAwB;AAC7C,SAAO,KAAK,UAAU,OAAO,CAAC,MAAM,MAAM;AACxC,QAAI,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,GAAG;AACnD,aAAO,OAAO,KAAK,CAA4B,EAC5C,KAAK,EACL,OAAgC,CAAC,KAAK,MAAM;AAC3C,YAAI,CAAC,IAAK,EAA8B,CAAC;AACzC,eAAO;AAAA,MACT,GAAG,CAAC,CAAC;AAAA,IACT;AACA,WAAO;AAAA,EACT,CAAC;AACH;;;ACpIA,OAAOC,YAAU;AAkBV,SAAS,gBAAgB,SAAiB,SAA+B;AAC9E,MAAI,YAAY,iBAAiB;AAC/B,WAAO;AAAA,MACL,cAAcC,OAAK,KAAK,SAAS,YAAY;AAAA,MAC7C,YAAYA,OAAK,KAAK,SAAS,eAAe;AAAA,MAC9C,iBAAiBA,OAAK,KAAK,SAAS,qBAAqB;AAAA,MACzD,qBAAqBA,OAAK,KAAK,SAAS,iBAAiB;AAAA,MACzD,sBAAsBA,OAAK,KAAK,SAAS,0BAA0B;AAAA,IACrE;AAAA,EACF;AACA,SAAO;AAAA,IACL,cAAcA,OAAK,KAAK,SAAS,GAAG,OAAO,OAAO;AAAA,IAClD,YAAYA,OAAK,KAAK,SAAS,UAAU,OAAO,SAAS;AAAA,IACzD,iBAAiBA,OAAK,KAAK,SAAS,gBAAgB,OAAO,SAAS;AAAA,IACpE,qBAAqBA,OAAK,KAAK,SAAS,cAAc,OAAO,OAAO;AAAA,IACpE,sBAAsBA,OAAK,KAAK,SAAS,qBAAqB,OAAO,SAAS;AAAA,EAChF;AACF;AAUO,IAAM,WAAN,MAAe;AAAA,EACZ,WAAW,oBAAI,IAA4B;AAAA,EAEnD,OAAO,KAA2B;AAChC,SAAK,SAAS,IAAI,IAAI,MAAM,GAAG;AAAA,EACjC;AAAA,EAEA,IACE,MACA,MACgB;AAChB,UAAM,MAAsB;AAAA,MAC1B;AAAA,MACA,OAAO,KAAK,SAAS,SAAS,IAAI;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,IACpB;AACA,SAAK,SAAS,IAAI,MAAM,GAAG;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,MAA0C;AAC5C,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EAC/B;AAAA,EAEA,IAAI,MAAuB;AACzB,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EAC/B;AAAA,EAEA,OAAiB;AACf,WAAO,CAAC,GAAG,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK;AAAA,EACxC;AAAA,EAEA,kBAAkB,MAAc,OAAsC;AACpE,UAAM,MAAM,KAAK,SAAS,IAAI,IAAI;AAClC,QAAI,IAAK,KAAI,cAAc;AAAA,EAC7B;AACF;AAKO,SAAS,mBAAmB,KAAmC;AACpE,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,SAAO,IACJ,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,eAAe;AACxD;;;ACrEA,SAAS,YAAYC,YAAU;AAC/B,OAAOC,SAAQ;AACf,OAAOC,YAAU;AACjB;AAAA,EACE;AAAA,OAIK;AAEP,IAAM,kBAAkB;AACxB,IAAM,gBAAgB;AAItB,SAAS,WAAmB;AAC1B,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAY,SAAS,SAAS,EAAG,QAAOA,OAAK,QAAQ,QAAQ;AACjE,SAAOA,OAAK,KAAKD,IAAG,QAAQ,GAAG,OAAO;AACxC;AAEO,SAAS,eAAuB;AACrC,SAAOC,OAAK,KAAK,SAAS,GAAG,eAAe;AAC9C;AAEO,SAAS,mBAA2B;AACzC,SAAOA,OAAK,KAAK,SAAS,GAAG,oBAAoB;AACnD;AAOA,SAAS,gBAAwB;AAC/B,SAAOA,OAAK,KAAK,SAAS,GAAG,WAAW;AAC1C;AA+CO,SAAS,aAAqB;AACnC,SAAOA,OAAK,KAAK,SAAS,GAAG,SAAS;AACxC;AAEA,SAAS,YAAY,GAAyB;AAC5C,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACnD;AAKA,SAAS,kBAAkB,KAAuC;AAChE,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,GAAG;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAM,IAAI;AACV,QAAM,QAAQ,EAAE;AAChB,MACE,OAAO,EAAE,YAAY,YACrB,OAAO,EAAE,gBAAgB,YACzB,CAAC,YAAY,EAAE,GAAG,KACjB,EAAE,WAAW,aAAa,EAAE,WAAW,aACxC,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,gBAAgB,YACzB,CAAC,SACD,CAAC,YAAY,MAAM,IAAI,KACvB,CAAC,YAAY,MAAM,IAAI,KACvB,CAAC,YAAY,MAAM,GAAG,GACtB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,EAAE;AAAA,IACX,aAAa,EAAE;AAAA,IACf,KAAK,EAAE;AAAA,IACP,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI;AAAA,IAC5D,WAAW,EAAE;AAAA,IACb,aAAa,EAAE;AAAA,EACjB;AACF;AAYA,IAAM,wBAAwC,EAAE,YAAY,kBAAkB;AAU9E,eAAsB,gBACpB,QAAwB,uBACK;AAC7B,QAAM,MAAM,WAAW;AACvB,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,KAAG,QAAQ,GAAG;AAAA,EAC9B,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;AAAA,EACR;AACA,QAAM,MAA0B,CAAC;AACjC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,UAAM,OAAOC,OAAK,KAAK,KAAK,IAAI;AAChC,QAAI;AACJ,QAAI;AACF,YAAM,MAAMD,KAAG,SAAS,MAAM,MAAM;AAAA,IACtC,QAAQ;AACN;AAAA,IACF;AACA,UAAM,SAAS,kBAAkB,GAAG;AACpC,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,OAAO,WAAW,aAAa,MAAM,WAAW,OAAO,GAAG;AACvE,QAAI,KAAK,EAAE,QAAQ,MAAM,QAAQ,KAAK,CAAC;AAAA,EACzC;AACA,MAAI,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,QAAQ,cAAc,EAAE,OAAO,OAAO,CAAC;AACnE,SAAO;AACT;AAQA,eAAsB,mBAAmB,QAA+B;AACtE,QAAMA,KAAG,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACxC;AA0CA,eAAsB,oBACpB,QAAwB,uBACG;AAC3B,QAAM,aAAa,MAAM,gBAAgB,KAAK;AAC9C,QAAM,SAAS,oBAAI,IAA4B;AAC/C,aAAW,KAAK,YAAY;AAC1B,UAAM,MAAM,MAAM,qBAAqB,EAAE,OAAO,WAAW;AAC3D,WAAO,IAAI,KAAK;AAAA,MACd,SAAS,EAAE,OAAO;AAAA,MAClB,aAAa,EAAE,OAAO;AAAA,MACtB,OAAO,EAAE,OAAO,YAAY;AAAA,MAC5B,OAAO,EAAE,OAAO;AAAA,MAChB,KAAK,EAAE,OAAO;AAAA,IAChB,CAAC;AAAA,EACH;AAIA,MAAI,SAA0B,CAAC;AAC/B,MAAI;AACF,cAAU,MAAM,aAAa,GAAG;AAAA,EAClC,QAAQ;AAGN,aAAS,CAAC;AAAA,EACZ;AACA,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,MAAM,qBAAqB,MAAM,IAAI;AACjD,QAAI,OAAO,IAAI,GAAG,EAAG;AACrB,WAAO,IAAI,KAAK;AAAA,MACd,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,OAAO;AAAA,MACP,gBAAgB,MAAM;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,cAAc,EAAE,OAAO,CAAC;AAC/E;AAKA,eAAsB,oBACpB,MACA,QAAwB,uBACe;AACvC,QAAM,aAAa,MAAM,gBAAgB,KAAK;AAC9C,SAAO,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,YAAY,IAAI;AACzD;AAMO,SAAS,iBAAiB,KAAsB;AACrD,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAC3B,WAAO;AAAA,EACT,SAAS,KAAK;AAGZ,QAAK,IAA8B,SAAS,QAAS,QAAO;AAC5D,UAAM;AAAA,EACR;AACF;AAgCA,SAAS,kBAAkB,KAAsB;AAC/C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AAIZ,WAAQ,IAA8B,SAAS;AAAA,EACjD;AACF;AAEA,eAAe,YAAY,MAA2C;AACpE,MAAI;AACF,UAAM,MAAM,MAAMA,KAAG,SAAS,MAAM,MAAM;AAC1C,UAAM,MAAM,OAAO,SAAS,IAAI,KAAK,GAAG,EAAE;AAC1C,WAAO,OAAO,UAAU,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,yBAA0C;AAAA,EAC9C,YAAY;AAAA,EACZ,mBAAmB,MAAM,YAAY,cAAc,CAAC;AAAA,EACpD,MAAM,iBAAmC;AACvC,UAAM,OAAO,QAAQ,IAAI,gBAAgB;AACzC,QAAI;AAKF,YAAM,MAAM,GAAG,IAAI,WAAW,EAAE,QAAQ,YAAY,QAAQ,GAAG,EAAE,CAAC;AAClE,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAIA,eAAe,YAAY,UAA+C;AACxE,SAAO,YAAY,QAAQ;AAC7B;AAOA,eAAsB,mBACpB,UACA,QAAyB,wBACJ;AACrB,QAAM,UAAU,MAAM,YAAY,QAAQ;AAC1C,QAAM,YAAY,MAAM,MAAM,kBAAkB;AAChD,MACE,cAAc,UACd,cAAc,QAAQ,OACtB,MAAM,WAAW,SAAS;AAAA;AAAA;AAAA,GAIzB,cAAc,WAAY,MAAM,MAAM,eAAe,IACtD;AACA,WAAO,EAAE,MAAM,UAAU,KAAK,UAAU;AAAA,EAC1C;AACA,MAAI,YAAY,UAAa,YAAY,QAAQ,OAAO,MAAM,WAAW,OAAO,GAAG;AACjF,WAAO,EAAE,MAAM,WAAW,KAAK,QAAQ;AAAA,EACzC;AACA,SAAO,EAAE,MAAM,QAAQ;AACzB;AAEO,SAAS,kBAAkB,QAAoB,UAAkB,WAA2B;AACjG,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aACE,wBAAwB,OAAO,GAAG;AAAA,IAGtC,KAAK;AACH,aACE,6BAA6B,OAAO,GAAG;AAAA,IAG3C,KAAK;AACH,aACE,kCAAkC,SAAS,kBAAkB,QAAQ;AAAA,EAG3E;AACF;AAQA,eAAsB,qBAAqB,OAAgC;AACzE,QAAM,WAAWC,OAAK,QAAQ,KAAK;AACnC,MAAI;AACF,WAAO,MAAMD,KAAG,SAAS,QAAQ;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,gBAAgB,QAAgB,UAAiC;AACrF,QAAMA,KAAG,MAAMC,OAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,QAAM,MAAM,GAAG,MAAM,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC5F,QAAM,KAAK,MAAMD,KAAG,KAAK,KAAK,GAAG;AACjC,MAAI;AACF,UAAM,GAAG,UAAU,UAAU,MAAM;AACnC,UAAM,GAAG,KAAK;AAAA,EAChB,UAAE;AACA,UAAM,GAAG,MAAM;AAAA,EACjB;AACA,QAAMA,KAAG,OAAO,KAAK,MAAM;AAC7B;AAEA,eAAe,YACb,UACA,YAAoB,iBACpB,QAAyB,wBACV;AACf,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAMA,KAAG,MAAMC,OAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,MAAI,eAAe;AACnB,SAAO,MAAM;AACX,QAAI;AACF,YAAM,KAAK,MAAMD,KAAG,KAAK,UAAU,IAAI;AACvC,UAAI;AAIF,cAAM,GAAG,UAAU,GAAG,QAAQ,GAAG;AAAA,GAAM,MAAM;AAAA,MAC/C,UAAE;AACA,cAAM,GAAG,MAAM;AAAA,MACjB;AACA;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,SAAU,OAAM;AAK7B,UAAI,CAAC,cAAc;AACjB,uBAAe;AACf,cAAM,SAAS,MAAM,mBAAmB,UAAU,KAAK;AACvD,YAAI,OAAO,SAAS,SAAU,OAAM,IAAI,MAAM,kBAAkB,QAAQ,UAAU,SAAS,CAAC;AAAA,MAC9F;AACA,UAAI,KAAK,IAAI,KAAK,UAAU;AAC1B,cAAM,SAAS,MAAM,mBAAmB,UAAU,KAAK;AACvD,cAAM,IAAI,MAAM,kBAAkB,QAAQ,UAAU,SAAS,CAAC;AAAA,MAChE;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AAEA,eAAe,YAAY,UAAiC;AAC1D,QAAMA,KAAG,OAAO,QAAQ,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAC1C;AAEA,eAAe,SAAY,IAAkC;AAC3D,QAAM,OAAO,iBAAiB;AAC9B,QAAM,YAAY,IAAI;AACtB,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,UAAM,YAAY,IAAI;AAAA,EACxB;AACF;AASA,eAAsB,eAAsC;AAC1D,QAAM,OAAO,aAAa;AAC1B,MAAI;AACJ,MAAI;AACF,UAAM,MAAMA,KAAG,SAAS,MAAM,MAAM;AAAA,EACtC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAAA,IACpC;AACA,UAAM;AAAA,EACR;AACA,QAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,SAAO,mBAAmB,MAAM,MAAM;AACxC;AAEA,eAAe,cAAc,KAAkC;AAG7D,QAAM,YAAY,mBAAmB,MAAM,GAAG;AAC9C,QAAM,gBAAgB,aAAa,GAAG,KAAK,UAAU,WAAW,MAAM,CAAC,IAAI,IAAI;AACjF;AASO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAC1C;AAAA,EACT,YAAY,MAAc;AACxB,UAAM,mCAAmC,IAAI,yBAAyB;AACtE,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACrB;AACF;AASA,eAAsB,WAAW,MAAiD;AAChF,QAAM,eAAe,MAAM,qBAAqB,KAAK,IAAI;AACzD,SAAO,SAAS,YAAY;AAC1B,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,SAAS,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI;AAC5D,UAAM,SAAS,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AAE/D,QAAI,UAAU,OAAO,SAAS,cAAc;AAC1C,YAAM,IAAI,0BAA0B,KAAK,IAAI;AAAA,IAC/C;AAEA,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,QAAI,UAAU,OAAO,SAAS,cAAc;AAG1C,aAAO,aAAa;AACpB,UAAI,KAAK,UAAW,QAAO,YAAY,KAAK;AAC5C,UAAI,KAAK,OAAQ,QAAO,SAAS,KAAK;AACtC,YAAM,cAAc,GAAG;AACvB,aAAO;AAAA,IACT;AAEA,QAAI,UAAU,OAAO,SAAS,KAAK,MAAM;AAGvC,YAAM,IAAI,0BAA0B,OAAO,IAAI;AAAA,IACjD;AAEA,UAAM,QAAuB;AAAA,MAC3B,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,MACN,cAAc;AAAA,MACd,WAAW,KAAK,aAAa,CAAC;AAAA,MAC9B,QAAQ,KAAK,UAAU;AAAA,IACzB;AACA,QAAI,SAAS,KAAK,KAAK;AACvB,UAAM,cAAc,GAAG;AACvB,WAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,WAAW,MAAkD;AACjF,QAAM,MAAM,MAAM,aAAa;AAC/B,SAAO,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACjD;AAEA,eAAsB,eAAyC;AAC7D,QAAM,MAAM,MAAM,aAAa;AAC/B,SAAO,IAAI;AACb;AAEA,eAAsB,UAAU,MAAc,QAAgD;AAC5F,SAAO,SAAS,YAAY;AAC1B,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,QAAQ,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACtD,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,oCAAoC,IAAI,GAAG;AACvE,UAAM,SAAS;AACf,UAAM,cAAc,GAAG;AACvB,WAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,cAAc,MAAc,MAAa,oBAAI,KAAK,GAAE,YAAY,GAAkB;AACtG,QAAM,SAAS,YAAY;AACzB,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,QAAQ,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACtD,QAAI,CAAC,MAAO;AACZ,UAAM,aAAa;AACnB,UAAM,cAAc,GAAG;AAAA,EACzB,CAAC;AACH;AAQA,eAAsB,cAAc,MAAkD;AACpF,SAAO,SAAS,YAAY;AAC1B,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,MAAM,IAAI,SAAS,UAAU,CAAC,MAAM,EAAE,SAAS,IAAI;AACzD,QAAI,MAAM,EAAG,QAAO;AACpB,UAAM,CAAC,OAAO,IAAI,IAAI,SAAS,OAAO,KAAK,CAAC;AAC5C,UAAM,cAAc,GAAG;AACvB,WAAO;AAAA,EACT,CAAC;AACH;AAqBA,IAAME,UAAS,KAAK,KAAK,KAAK;AAEvB,IAAM,uBAAuB,IAAIA;AAEjC,SAAS,aAAqB;AACnC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,OAAO,SAAS,KAAK,EAAE;AACjC,MAAI,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO;AACzC,UAAQ;AAAA,IACN,0DAA0D,GAAG,oBAAoB,oBAAoB;AAAA,EACvG;AACA,SAAO;AACT;AAOA,eAAe,eAAe,GAAgC;AAC5D,MAAI;AACF,UAAM,OAAO,MAAMF,KAAG,KAAK,CAAC;AAC5B,WAAO,KAAK,YAAY,IAAI,YAAY;AAAA,EAC1C,SAAS,KAAK;AACZ,WAAQ,IAA8B,SAAS,WAAW,SAAS;AAAA,EACrE;AACF;AA0BA,eAAsB,cAAc,OAAqB,CAAC,GAA6B;AACrF,QAAM,QAAQ,KAAK,SAAS,WAAW;AACvC,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,MAAM,KAAK,OAAO,KAAK;AAE7B,SAAO,SAAS,YAAY;AAC1B,UAAM,MAAM,MAAM,aAAa;AAC/B,UAAM,UAA2B,CAAC;AAClC,UAAM,OAAwB,CAAC;AAC/B,eAAW,SAAS,IAAI,UAAU;AAChC,YAAM,SAAS,MAAM,SAAS,MAAM,IAAI;AACxC,UAAI,WAAW,QAAQ;AAGrB,aAAK,KAAK,KAAK;AACf;AAAA,MACF;AAGA,UAAI,SAAS,GAAG;AACd,gBAAQ,KAAK,KAAK;AAClB;AAAA,MACF;AACA,YAAM,WAAW,KAAK,MAAM,MAAM,cAAc,MAAM,YAAY;AAClE,YAAM,MAAM,IAAI,KAAK,OAAO,SAAS,QAAQ,IAAI,WAAW;AAC5D,UAAI,MAAM,OAAO;AACf,gBAAQ,KAAK,KAAK;AAAA,MACpB,OAAO;AACL,aAAK,KAAK,KAAK;AAAA,MACjB;AAAA,IACF;AACA,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,QAAI,WAAW;AACf,UAAM,cAAc,GAAG;AACvB,WAAO;AAAA,EACT,CAAC;AACH;;;ACtxBA,OAAO,aAIA;AACP,OAAO,UAAU;AASjB,SAAS,sBAAsB,yBAAyB,4BAA4B;;;ACdpF,SAAS,YAAYG,YAAU;AAC/B,OAAOC,YAAU;AACjB,OAAOC,SAAQ;AACf,SAAS,WAAW,iBAAiB,QAAQ,oBAAoB;;;ACejE,SAAS,YAAYC,YAAU;AAC/B,OAAOC,YAAU;AACjB,SAAS,aAAa;AAsBtB,IAAM,oBAID;AAAA,EACH,EAAE,UAAU,aAAa,IAAI,OAAO,MAAM,CAAC,WAAW,cAAc,EAAE;AAAA,EACtE,EAAE,UAAU,kBAAkB,IAAI,QAAQ,MAAM,CAAC,WAAW,cAAc,EAAE;AAAA,EAC5E,EAAE,UAAU,aAAa,IAAI,QAAQ,MAAM,CAAC,WAAW,UAAU,EAAE;AAAA,EACnE;AAAA,IACE,UAAU;AAAA,IACV,IAAI;AAAA,IACJ,MAAM,CAAC,WAAW,cAAc,aAAa,kBAAkB;AAAA,EACjE;AACF;AAMA,IAAM,oBAAoB,CAAC,WAAW,cAAc,aAAa,kBAAkB;AAEnF,eAAeC,QAAO,GAA6B;AACjD,MAAI;AACF,UAAMF,KAAG,OAAO,CAAC;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,qBACpB,YACgC;AAChC,MAAI,MAAMC,OAAK,QAAQ,UAAU;AACjC,QAAM,QAAQ,oBAAI,IAAY;AAC9B,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,MAAM,IAAI,GAAG,EAAG;AACpB,UAAM,IAAI,GAAG;AACb,eAAW,aAAa,mBAAmB;AACzC,YAAM,WAAWA,OAAK,KAAK,KAAK,UAAU,QAAQ;AAClD,UAAI,MAAMC,QAAO,QAAQ,GAAG;AAC1B,eAAO,EAAE,IAAI,UAAU,IAAI,KAAK,KAAK,MAAM,CAAC,GAAG,UAAU,IAAI,EAAE;AAAA,MACjE;AAAA,IACF;AACA,UAAM,SAASD,OAAK,QAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO,EAAE,IAAI,OAAO,KAAKA,OAAK,QAAQ,UAAU,GAAG,MAAM,CAAC,GAAG,iBAAiB,EAAE;AAClF;AAmBA,eAAsB,yBACpB,KACmC;AACnC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,IAAI,IAAI,IAAI,MAAM;AAAA,MACpC,KAAK,IAAI;AAAA;AAAA,MAET,KAAK,QAAQ;AAAA;AAAA;AAAA,MAGb,OAAO;AAAA,MACP,OAAO,CAAC,UAAU,UAAU,MAAM;AAAA,IACpC,CAAC;AACD,QAAI,SAAS;AACb,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC1C,gBAAU,MAAM,SAAS,MAAM;AAAA,IACjC,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,cAAQ;AAAA,QACN,IAAI,IAAI;AAAA,QACR,KAAK,IAAI;AAAA,QACT,MAAM,IAAI;AAAA,QACV,UAAU;AAAA,QACV,QAAQ,SAAS;AAAA,EAAK,IAAI,OAAO;AAAA,MACnC,CAAC;AAAA,IACH,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,cAAQ;AAAA,QACN,IAAI,IAAI;AAAA,QACR,KAAK,IAAI;AAAA,QACT,MAAM,IAAI;AAAA,QACV,UAAU,QAAQ;AAAA,QAClB,QAAQ,OAAO,KAAK;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;;;ADzFA,eAAeE,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMC,KAAG,OAAO,CAAC;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,gBAAgB,UAAwC;AACrE,QAAM,UAAUC,OAAK,KAAK,UAAU,cAAc;AAClD,QAAM,MAAM,MAAMD,KAAG,SAAS,SAAS,MAAM;AAC7C,SAAO,KAAK,MAAM,GAAG;AACvB;AAEA,eAAe,cAAc,UAAqC;AAChE,QAAM,UAAU,MAAMA,KAAG,QAAQ,QAAQ;AACzC,SAAO,QACJ;AAAA,IACC,CAAC,OACE,EAAE,WAAW,iBAAiB,KAAK,EAAE,WAAW,WAAW,MAC5D,aAAa,KAAK,CAAC;AAAA,EACvB,EACC,KAAK;AACV;AAEA,SAAS,gBAAwB;AAC/B,SAAO,QAAQ,IAAI,mBAAmBC,OAAK,KAAKC,IAAG,QAAQ,GAAG,SAAS,mBAAmB;AAC5F;AAEA,eAAe,gBAAgB,OAAsC;AACnE,QAAM,UAAU,cAAc;AAC9B,QAAMF,KAAG,MAAMC,OAAK,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,QAAMD,KAAG,WAAW,SAAS,KAAK,UAAU,KAAK,IAAI,MAAM,MAAM;AACnE;AAKA,SAAS,eAAe,aAAqBG,UAAgC;AAC3E,MAAI,YAAY,SAAS,2BAA2B,GAAG;AACrD,WAAO,YAAY,QAAQ,6BAA6B,GAAGA,QAAO;AAAA,0BAA6B;AAAA,EACjG;AAEA,QAAM,QAAQ,YAAY,MAAM,IAAI;AAEpC,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,EAAE,SAAS,wBAAwB,EAAG,eAAc;AAAA,EACjE;AACA,MAAI,eAAe,GAAG;AACpB,UAAM,OAAO,cAAc,GAAG,GAAGA,QAAO;AACxC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,EAAE,SAAS,cAAc,GAAG;AACrC,YAAM,OAAO,GAAG,GAAGA,QAAO;AAC1B,aAAO,MAAM,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,mBAAmB,KAAsD;AAC7F,QAAM,MAAM,MAAM,gBAAgB,IAAI,QAAQ;AAC9C,QAAM,UAAU,EAAE,GAAI,IAAI,gBAAgB,CAAC,GAAI,GAAI,IAAI,mBAAmB,CAAC,EAAG;AAE9E,QAAM,UAAmC,CAAC;AAC1C,aAAW,CAAC,SAAS,gBAAgB,KAAK,OAAO,QAAQ,OAAO,GAAG;AACjE,UAAM,QAAQ,gBAAgB,SAAS,gBAAgB;AACvD,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,aAAa,aAAa,MAAM,aAAa,YAAa;AACpE,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,yBAAyB,MAAM;AAAA,MAC/B,iBAAiB,MAAM;AAAA,MACvB,cAAc,MAAM;AAAA,MACpB,OAAO,MAAM;AAAA,IACf,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,SAAS,sBACd,SACA,kBAC8B;AAC9B,QAAM,QAAQ,gBAAgB,SAAS,gBAAgB;AACvD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,UAAU,MAAM;AAAA,IAChB,yBAAyB,MAAM;AAAA,IAC/B,iBAAiB,MAAM;AAAA,IACvB,cAAc,MAAM;AAAA,IACpB,OAAO,MAAM;AAAA,EACf;AACF;AAEA,eAAsB,+BACpB,KACsC;AACtC,QAAM,YAAY,MAAM,cAAc,IAAI,QAAQ;AAClD,QAAM,UAAU,MAAMJ,YAAWE,OAAK,KAAK,IAAI,UAAU,WAAW,CAAC;AAErE,QAAM,wBAAwB,IAAI;AAAA,IAChC,aAAa,EACV,IAAI,CAAC,MAAM,EAAE,uBAAuB,EACpC,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,EACnC;AAEA,QAAM,MAAM,MAAM,gBAAgB,IAAI,QAAQ;AAC9C,QAAM,UAAU,EAAE,GAAI,IAAI,gBAAgB,CAAC,GAAI,GAAI,IAAI,mBAAmB,CAAC,EAAG;AAE9E,QAAM,gBAAwC,CAAC;AAC/C,aAAW,CAAC,KAAK,OAAO,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,QAAI,IAAI,WAAW,iBAAiB,KAAK,sBAAsB,IAAI,GAAG,GAAG;AACvE,oBAAc,GAAG,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,SAAS,cAAc;AAC7C;AAEA,eAAsB,eACpB,KACA,MAMA,SAG+B;AAC/B,QAAM,YAAY,MAAM,cAAc,IAAI,QAAQ;AAElD,MAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,0CAA0C,IAAI,QAAQ;AAAA,IACxD;AAAA,EACF;AAGA,aAAW,QAAQ,WAAW;AAC5B,UAAM,UAAU,MAAMD,KAAG,SAASC,OAAK,KAAK,IAAI,UAAU,IAAI,GAAG,MAAM;AACvE,QAAI,QAAQ,SAAS,KAAK,oBAAoB,GAAG;AAC/C,aAAO,EAAE,SAAS,KAAK,SAAS,cAAc,CAAC,GAAG,WAAW,CAAC,GAAG,eAAe,IAAI,gBAAgB,KAAK;AAAA,IAC3G;AAAA,EACF;AAEA,QAAM,cAAc,UAAU,CAAC;AAC/B,QAAM,cAAcA,OAAK,KAAK,IAAI,UAAU,WAAW;AACvD,QAAM,eAAyB,CAAC;AAChC,QAAM,YAAsB,CAAC;AAG7B,QAAM,UAAUA,OAAK,KAAK,IAAI,UAAU,cAAc;AACtD,QAAM,MAAM,MAAM,gBAAgB,IAAI,QAAQ;AAC9C,MAAI,EAAE,IAAI,gBAAgB,CAAC,GAAG,KAAK,uBAAuB,GAAG;AAC3D,QAAI,eAAe,EAAE,GAAI,IAAI,gBAAgB,CAAC,GAAI,CAAC,KAAK,uBAAuB,GAAG,KAAK,QAAQ;AAC/F,UAAMD,KAAG,UAAU,SAAS,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM;AACvE,iBAAa,KAAK,cAAc;AAChC,cAAU,KAAK,GAAG,KAAK,uBAAuB,IAAI,KAAK,OAAO,EAAE;AAAA,EAClE;AAGA,QAAM,cAAc,MAAMA,KAAG,SAAS,aAAa,MAAM;AACzD,QAAM,UAAU,eAAe,aAAa,KAAK,oBAAoB;AACrE,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,qDAAqD,WAAW;AAAA,IAElE;AAAA,EACF;AACA,QAAMA,KAAG,UAAU,aAAa,SAAS,MAAM;AAC/C,eAAa,KAAK,WAAW;AAG7B,QAAM,MAAM,MAAM,qBAAqB,IAAI,QAAQ;AACnD,QAAM,YAAY,SAAS,cAAc;AACzC,QAAM,UAAU,MAAM,UAAU,GAAG;AACnC,QAAM,gBACJ,QAAQ,aAAa,IACjB,GAAG,IAAI,EAAE,uBACT,QAAQ,UAAU,GAAG,IAAI,EAAE,yBAAyB,QAAQ,QAAQ;AAG1E,QAAM,gBAAgB;AAAA,IACpB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,SAAS,IAAI;AAAA,IACb,SAAS,KAAK;AAAA,IACd,yBAAyB,KAAK;AAAA,IAC9B,SAAS,KAAK;AAAA,IACd,sBAAsB,KAAK;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO,EAAE,SAAS,KAAK,SAAS,cAAc,WAAW,eAAe,gBAAgB,MAAM;AAChG;AAEA,eAAsB,gBACpB,KACA,MAMwB;AACxB,QAAM,YAAY,MAAM,cAAc,IAAI,QAAQ;AAElD,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,cAAc,CAAC;AAAA,MACf,WAAW,CAAC;AAAA,MACZ,kBAAkB,CAAC;AAAA,MACnB,eAAe;AAAA,IACjB;AAAA,EACF;AAEA,aAAW,QAAQ,WAAW;AAC5B,UAAM,UAAU,MAAMA,KAAG,SAASC,OAAK,KAAK,IAAI,UAAU,IAAI,GAAG,MAAM;AACvE,QAAI,QAAQ,SAAS,KAAK,oBAAoB,GAAG;AAC/C,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd,cAAc,CAAC;AAAA,QACf,WAAW,CAAC;AAAA,QACZ,kBAAkB,CAAC;AAAA,QACnB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,UAAU,CAAC;AAC/B,QAAM,eAAyB,CAAC;AAChC,QAAM,YAAsB,CAAC;AAC7B,MAAI,mBAA2B,CAAC;AAChC,MAAI,gBAAgB;AAEpB,QAAM,MAAM,MAAM,gBAAgB,IAAI,QAAQ;AAC9C,MAAI,EAAE,IAAI,gBAAgB,CAAC,GAAG,KAAK,uBAAuB,GAAG;AAC3D,uBAAmB,EAAE,cAAc,EAAE,CAAC,KAAK,uBAAuB,GAAG,KAAK,QAAQ,EAAE;AACpF,cAAU,KAAK,GAAG,KAAK,uBAAuB,IAAI,KAAK,OAAO,EAAE;AAChE,iBAAa,KAAK,cAAc;AAAA,EAClC;AAEA,QAAM,cAAc,MAAMD,KAAG,SAASC,OAAK,KAAK,IAAI,UAAU,WAAW,GAAG,MAAM;AAClF,QAAM,UAAU,eAAe,aAAa,KAAK,oBAAoB;AACrE,MAAI,SAAS;AACX,iBAAa,KAAK,WAAW;AAC7B,oBAAgB,KAAK,KAAK,oBAAoB;AAAA,EAChD,OAAO;AACL,oBAAgB;AAAA,EAClB;AAEA,SAAO,EAAE,SAAS,KAAK,SAAS,cAAc,WAAW,kBAAkB,cAAc;AAC3F;AAEA,eAAsB,kBACpB,KACA,MAC+C;AAC/C,QAAM,UAAU,cAAc;AAE9B,MAAI,CAAE,MAAMF,YAAW,OAAO,GAAI;AAChC,WAAO,EAAE,QAAQ,OAAO,SAAS,6BAA6B;AAAA,EAChE;AAEA,QAAM,MAAM,MAAMC,KAAG,SAAS,SAAS,MAAM;AAC7C,QAAM,UAA4B,IAC/B,KAAK,EACL,MAAM,IAAI,EACV,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAmB;AAEnD,QAAM,QAAQ,CAAC,GAAG,OAAO,EACtB,QAAQ,EACR,KAAK,CAAC,MAAM,EAAE,YAAY,IAAI,WAAW,EAAE,YAAY,KAAK,OAAO;AAEtE,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,QAAQ,OAAO,SAAS,6BAA6B;AAAA,EAChE;AAGA,QAAM,UAAUC,OAAK,KAAK,IAAI,UAAU,cAAc;AACtD,MAAI,MAAMF,YAAW,OAAO,GAAG;AAC7B,UAAM,MAAM,MAAM,gBAAgB,IAAI,QAAQ;AAC9C,QAAI,IAAI,eAAe,MAAM,uBAAuB,GAAG;AACrD,YAAM,EAAE,CAAC,MAAM,uBAAuB,GAAG,UAAU,GAAG,KAAK,IAAI,IAAI;AACnE,UAAI,eAAe;AACnB,YAAMC,KAAG,UAAU,SAAS,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM;AAAA,IACzE;AAAA,EACF;AAGA,QAAM,YAAY,MAAM,cAAc,IAAI,QAAQ;AAClD,aAAW,QAAQ,WAAW;AAC5B,UAAM,WAAWC,OAAK,KAAK,IAAI,UAAU,IAAI;AAC7C,UAAM,UAAU,MAAMD,KAAG,SAAS,UAAU,MAAM;AAClD,QAAI,QAAQ,SAAS,MAAM,oBAAoB,GAAG;AAChD,YAAM,WAAW,QACd,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,CAAC,KAAK,SAAS,MAAM,oBAAoB,CAAC,EAC3D,KAAK,IAAI;AACZ,YAAMA,KAAG,UAAU,UAAU,UAAU,MAAM;AAC7C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS,eAAe,MAAM,OAAO,KAAK,MAAM,uBAAuB;AAAA,EACzE;AACF;;;AEjXO,IAAM,mBAAmB;AACzB,IAAM,uBAAuB;AAQ7B,SAAS,UACd,KACA,OACA,MACM;AACN,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,kBAAkB,KAAK,mBAAmB;AAEhD,QAAM,IAAI,UAAU,gBAAgB,mBAAmB;AACvD,QAAM,IAAI,UAAU,iBAAiB,wBAAwB;AAC7D,QAAM,IAAI,UAAU,cAAc,YAAY;AAC9C,QAAM,IAAI,UAAU,qBAAqB,IAAI;AAC7C,QAAM,IAAI,eAAe;AAWzB,QAAM,IAAI,MAAM,WAAW;AAE3B,MAAI,UAAU;AACd,MAAI,UAAU;AAEd,QAAM,kBAAkB,MAAY;AAClC,QAAI,QAAS;AACb,cAAU;AACV,aAAS,IAAI,mBAAmB,QAAQ;AACxC,kBAAc,SAAS;AACvB,QAAI,CAAC,MAAM,IAAI,cAAe,OAAM,IAAI,IAAI;AAAA,EAC9C;AAEA,QAAM,aAAa,CAAC,UAAwB;AAC1C,QAAI,QAAS;AACb,QAAI,WAAW,iBAAiB;AAI9B,YAAM,WAAW;AAAA,QAAuB,KAAK,UAAU,EAAE,QAAQ,eAAe,CAAC,CAAC;AAAA;AAAA;AAClF,YAAM,IAAI,MAAM,QAAQ;AACxB,sBAAgB;AAChB;AAAA,IACF;AACA;AACA,UAAM,IAAI,MAAM,OAAO,MAAM;AAC3B,gBAAU,KAAK,IAAI,GAAG,UAAU,CAAC;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,CAAC,aAAsC;AACtD,QAAI,SAAS,YAAY,KAAK,QAAS;AACvC,eAAW,UAAU,SAAS,IAAI;AAAA,QAAW,KAAK,UAAU,SAAS,OAAO,CAAC;AAAA;AAAA,CAAM;AAAA,EACrF;AAEA,WAAS,GAAG,mBAAmB,QAAQ;AAEvC,QAAM,YAAY,YAAY,MAAM;AAClC,QAAI,QAAS;AACb,UAAM,IAAI,MAAM,gBAAgB;AAAA,EAClC,GAAG,WAAW;AACd,MAAI,OAAO,UAAU,UAAU,WAAY,WAAU,MAAM;AAE3D,MAAI,IAAI,GAAG,SAAS,eAAe;AACnC,QAAM,IAAI,GAAG,SAAS,eAAe;AACrC,QAAM,IAAI,GAAG,SAAS,eAAe;AACvC;;;AHIA,SAAS,eAAe,OAAmC;AACzD,QAAM,QAAqB,CAAC;AAC5B,QAAM,YAAY,CAAC,KAAK,UAAU;AAChC,UAAM,KAAK,KAAK;AAAA,EAClB,CAAC;AACD,QAAM,QAAqB,CAAC;AAC5B,QAAM,YAAY,CAAC,KAAK,UAAU;AAChC,UAAM,KAAK,KAAK;AAAA,EAClB,CAAC;AACD,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,SAAS,eAAe,KAAqB,eAAgC;AAG3E,QAAM,SAAS,IAAI;AACnB,QAAM,QAAQ,OAAO;AAQrB,MAAI,eAAe;AACjB,WAAO,UAAU,UAAa,UAAU,kBAAkB,gBAAgB;AAAA,EAC5E;AACA,SAAO,SAAS;AAClB;AAEA,SAAS,eACP,UACA,KACA,OACA,WACA,eACuB;AACvB,QAAM,OAAO,eAAe,KAAK,aAAa;AAC9C,QAAM,MAAM,SAAS,IAAI,IAAI;AAC7B,MAAI,CAAC,KAAK;AAGR,UAAM,QAAQ,WAAW,OAAO,IAAI;AACpC,QAAI,UAAU,iBAAiB;AAC7B,WAAK,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,SAAS,MAAM,QAAQ,gBAAgB,CAAC;AAClF,aAAO;AAAA,IACT;AACA,QAAI,UAAU,UAAU;AACtB,WAAK,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,SAAS,MAAM,QAAQ,SAAS,CAAC;AAC3E,aAAO;AAAA,IACT;AACA,SAAK,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,SAAS,KAAK,CAAC;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAiC;AAC5D,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,MAAI,CAAC,KAAK,OAAO;AACf,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,QAAM,WAAW,IAAI,SAAc;AAGnC,QAAM,QAAQ,gBAAgB,iBAAiB,EAAE;AACjD,WAAS,IAAI,iBAAiB;AAAA,IAC5B,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,OAAO;AAAA,MACL,cAAc,MAAM;AAAA,MACpB,YAAY,KAAK,cAAc,MAAM;AAAA,MACrC,iBAAiB,KAAK,mBAAmB,MAAM;AAAA,MAC/C,qBAAqB,MAAM;AAAA,MAC3B,sBAAsB,MAAM;AAAA,IAC9B;AAAA,IACA,aAAa,KAAK;AAAA,EACpB,CAAC;AACD,SAAO;AACT;AAiCA,SAAS,eAAe,OAAwB,KAAyB;AACvE,QAAM,EAAE,UAAU,WAAW,eAAe,mBAAmB,IAAI;AAMnE,QAAM,IAAsC,WAAW,CAAC,KAAK,UAAU;AACrE,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,cAAU,KAAK,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC;AAAA,EAC9C,CAAC;AAOD,MAAI,IAAI,UAAU,WAAW;AAC3B,UAAM,IAAsC,WAAW,OAAO,KAAK,UAAU;AAC3E,YAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,UAAI,CAAC,KAAM;AACX,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS,KAAK;AAAA,QACd;AAAA;AAAA;AAAA;AAAA,QAIA,QAAQ,KAAK,MAAM,WAAW,GAAI;AAAA,QAClC,WAAW,KAAK,MAAM;AAAA,QACtB,WAAW,KAAK,MAAM;AAAA,QACtB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,IAAsC,UAAU,OAAO,KAAK,UAAU;AAC1E,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,WAAO,eAAe,KAAK,KAAK;AAAA,EAClC,CAAC;AAED,QAAM;AAAA,IACJ;AAAA,IACA,OAAO,KAAK,UAAU;AACpB,YAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,UAAI,CAAC,KAAM;AACX,YAAM,EAAE,GAAG,IAAI,IAAI;AACnB,UAAI,CAAC,KAAK,MAAM,QAAQ,EAAE,GAAG;AAC3B,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,CAAC;AAAA,MAC7D;AACA,aAAO,EAAE,MAAM,KAAK,MAAM,kBAAkB,EAAE,EAAe;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM;AAAA,IACJ;AAAA,IACA,OAAO,KAAK,UAAU;AACpB,YAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,UAAI,CAAC,KAAM;AACX,YAAM,EAAE,GAAG,IAAI,IAAI;AACnB,UAAI,CAAC,KAAK,MAAM,QAAQ,EAAE,GAAG;AAC3B,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,CAAC;AAAA,MAC7D;AACA,YAAM,UAAU,KAAK,MAClB,aAAa,EAAE,EACf,IAAI,CAAC,MAAM,KAAK,MAAM,kBAAkB,CAAC,CAAc;AAC1D,YAAM,WAAW,KAAK,MACnB,cAAc,EAAE,EAChB,IAAI,CAAC,MAAM,KAAK,MAAM,kBAAkB,CAAC,CAAc;AAC1D,aAAO,EAAE,SAAS,SAAS;AAAA,IAC7B;AAAA,EACF;AAKA,QAAM,IAGH,+BAA+B,OAAO,KAAK,UAAU;AACtD,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,EAAE,OAAO,IAAI,IAAI;AACvB,QAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG;AAC/B,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,IAAI,OAAO,CAAC;AAAA,IACrE;AACA,UAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI;AAC1D,QAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,mCAAmC;AACrF,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO,mCAAmC,iCAAiC;AAAA,MAC7E,CAAC;AAAA,IACH;AACA,WAAO,0BAA0B,KAAK,OAAO,QAAQ,KAAK;AAAA,EAC5D,CAAC;AAOD,QAAM;AAAA,IACJ;AAAA,IACA,OAAO,KAAK,UAAU;AACpB,YAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,UAAI,CAAC,KAAM;AACX,YAAM,EAAE,OAAO,IAAI,IAAI;AACvB,UAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG;AAC/B,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,IAAI,OAAO,CAAC;AAAA,MACrE;AACA,aAAO,wBAAwB,KAAK,OAAO,MAAM;AAAA,IACnD;AAAA,EACF;AAKA,QAAM,IAGH,sBAAsB,OAAO,KAAK,UAAU;AAC7C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,QAAI,IAAI,MAAM,MAAM;AAClB,YAAM,aAAa,IAAI,MAAM,KAC1B,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,YAAM,SAA2B,CAAC;AAClC,iBAAW,KAAK,YAAY;AAC1B,cAAM,IAAI,qBAAqB,UAAU,CAAC;AAC1C,YAAI,CAAC,EAAE,SAAS;AACd,iBAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,YAC1B,OAAO,4BAA4B,CAAC;AAAA,YACpC,SAAS,qBAAqB;AAAA,UAChC,CAAC;AAAA,QACH;AACA,eAAO,KAAK,EAAE,IAAI;AAAA,MACpB;AACA,mBAAa,IAAI,IAAI,MAAM;AAAA,IAC7B;AACA,QAAI;AACJ,QAAI,IAAI,MAAM,kBAAkB,QAAW;AACzC,YAAM,IAAI,OAAO,IAAI,MAAM,aAAa;AACxC,UAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,KAAK,IAAI,GAAG;AACzC,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,UAC1B,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA,sBAAgB;AAAA,IAClB;AACA,WAAO,mBAAmB,KAAK,OAAO;AAAA,MACpC,GAAI,aAAa,EAAE,MAAM,WAAW,IAAI,CAAC;AAAA,MACzC,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACvD,GAAI,IAAI,MAAM,OAAO,EAAE,MAAM,IAAI,MAAM,KAAK,IAAI,CAAC;AAAA,IACnD,CAAC;AAAA,EACH,CAAC;AAKD,QAAM,IAGH,cAAc,OAAO,KAAK,UAAU;AACrC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,CAAC,MAAO,QAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC,EAAE;AACpD,UAAM,SAAS,MAAM,gBAAgB,KAAK;AAC1C,UAAM,QAAQ,OAAO;AACrB,UAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI;AAC1D,UAAM,YACJ,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,OAAO,GAAG,IAAI;AAC/D,UAAM,SAAS,OAAO,MAAM,GAAG,SAAS;AACxC,WAAO,EAAE,OAAO,OAAO,QAAQ,OAAO,QAAQ,OAAO;AAAA,EACvD,CAAC;AAED,QAAM,IAGH,iBAAiB,OAAO,KAAK,UAAU;AACxC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,mBAAmB,IAAI;AACrC,QAAI,CAAC,MAAO,QAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC,EAAE;AACpD,UAAM,SAAS,MAAM,gBAAgB,KAAK;AAC1C,UAAM,WAAW,IAAI,MAAM,WACvB,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,MAAM,QAAQ,IACtD;AACJ,UAAM,UAAU,CAAC,GAAG,QAAQ,EAAE,QAAQ;AACtC,UAAM,QAAQ,QAAQ;AACtB,UAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI;AAC1D,UAAM,SAAS,QAAQ,MAAM,GAAG,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ,EAAE;AAChF,WAAO,EAAE,OAAO,OAAO,QAAQ,OAAO,QAAQ,OAAO;AAAA,EACvD,CAAC;AAKD,QAAM,yBAAyB,OAC7B,KACA,UACgF;AAChF,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,EAAE,OAAO,IAAI,IAAI;AACvB,QAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG;AAC/B,YAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,IAAI,OAAO,CAAC;AAC5D;AAAA,IACF;AACA,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,CAAC,MAAO,QAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC,EAAE;AACpD,UAAM,SAAS,MAAM,gBAAgB,KAAK;AAC1C,UAAM,WAAW,OAAO;AAAA,MACtB,CAAC,MACC,EAAE,iBAAiB,UAAU,EAAE,YAAY,OAAO,QAAQ,aAAa,EAAE;AAAA,IAC7E;AACA,WAAO,EAAE,OAAO,SAAS,QAAQ,OAAO,SAAS,QAAQ,QAAQ,SAAS;AAAA,EAC5E;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AAEA,QAAM,IAGH,6BAA6B,OAAO,KAAK,UAAU;AACpD,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,EAAE,OAAO,IAAI,IAAI;AACvB,QAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG;AAC/B,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,IAAI,OAAO,CAAC;AAAA,IACrE;AAKA,UAAM,QAAQ,cAAc,IAAI;AAChC,UAAM,YAAY,QAAQ,MAAM,gBAAgB,KAAK,IAAI,CAAC;AAC1D,QAAI;AACJ,QAAI,IAAI,MAAM,SAAS;AACrB,mBAAa,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,MAAM,OAAO;AAC7D,UAAI,CAAC,YAAY;AACf,eAAO,MACJ,KAAK,GAAG,EACR,KAAK,EAAE,OAAO,yBAAyB,IAAI,IAAI,MAAM,QAAQ,CAAC;AAAA,MACnE;AAAA,IACF;AACA,UAAM,SAAS,aAAa,KAAK,OAAO,QAAQ,YAAY,SAAS;AACrE,QAAI,CAAC,OAAQ,QAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,uBAAuB,IAAI,OAAO,CAAC;AACrF,WAAO;AAAA,EACT,CAAC;AAED,QAAM,IAGH,+BAA+B,OAAO,KAAK,UAAU;AACtD,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,EAAE,OAAO,IAAI,IAAI;AACvB,QAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG;AAC/B,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,IAAI,OAAO,CAAC;AAAA,IACrE;AACA,UAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI;AAC1D,QAAI,UAAU,WAAc,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI;AACjE,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,sCAAsC,CAAC;AAAA,IAC9E;AACA,WAAO,eAAe,KAAK,OAAO,QAAQ,KAAK;AAAA,EACjD,CAAC;AAED,QAAM,IAGH,WAAW,OAAO,KAAK,UAAU;AAClC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,IAAI,MAAM,KAAK,IAAI,KAAK;AACrC,QAAI,CAAC,IAAK,QAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,kCAAkC,CAAC;AAClF,UAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI;AAC1D,UAAM,YACJ,UAAU,UAAa,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AACvE,QAAI,KAAK,aAAa;AACpB,YAAM,SAAS,MAAM,KAAK,YAAY,OAAO,KAAK,SAAS;AAC3D,aAAO;AAAA,QACL,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,QACjB,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE;AAAA,MACpE;AAAA,IACF;AACA,UAAM,IAAI,IAAI,YAAY;AAC1B,UAAM,UAA6C,CAAC;AACpD,SAAK,MAAM,YAAY,CAAC,IAAI,UAAU;AACpC,YAAM,OAAQ,MAA4B,QAAQ;AAClD,UAAI,GAAG,YAAY,EAAE,SAAS,CAAC,KAAK,KAAK,YAAY,EAAE,SAAS,CAAC,GAAG;AAClE,gBAAQ,KAAK,EAAE,GAAI,OAAqB,OAAO,EAAE,CAAC;AAAA,MACpD;AAAA,IACF,CAAC;AACD,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS,QAAQ,MAAM,GAAG,SAAS;AAAA,IACrC;AAAA,EACF,CAAC;AAED,QAAM;AAAA,IACJ;AAAA,IACA,OAAO,KAAK,UAAU;AACpB,YAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,UAAI,CAAC,KAAM;AACX,YAAM,UAAU,IAAI,MAAM;AAC1B,UAAI,CAAC,SAAS;AACZ,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,wCAAwC,CAAC;AAAA,MAChF;AACA,UAAI;AACF,cAAM,WAAW,MAAM,oBAAoB,OAAO;AAClD,eAAO,iBAAiB,KAAK,OAAO,QAAQ;AAAA,MAC9C,SAAS,KAAK;AACZ,eAAO,MACJ,KAAK,GAAG,EACR,KAAK,EAAE,OAAO,2BAA2B,SAAS,QAAS,IAAc,QAAQ,CAAC;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AAOA,QAAM,KAGH,aAAa,OAAO,KAAK,UAAU;AACpC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,UAAU;AACvD,aAAO,MACJ,KAAK,GAAG,EACR,KAAK,EAAE,OAAO,uDAAuD,CAAC;AAAA,IAC3E;AACA,UAAM,OAAO,KAAK;AAClB,QAAI,OAAO,KAAK,kBAAkB,YAAY,KAAK,kBAAkB,gBAAgB;AACnF,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO,sCAAsC,KAAK,aAAa,cAAc,cAAc;AAAA,MAC7F,CAAC;AAAA,IACH;AACA,QAAI;AACF,YAAM,SAAS,cAAc,KAAK,OAAO,IAAI;AAC7C,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd,YAAY,OAAO;AAAA,QACnB,YAAY,OAAO;AAAA,QACnB,WAAW,KAAK,MAAM;AAAA,QACtB,WAAW,KAAK,MAAM;AAAA,MACxB;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,SAAU,IAAc;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,KAAuC,eAAe,OAAO,KAAK,UAAU;AAChF,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MACJ,KAAK,GAAG,EACR,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACpF;AACA,UAAM,SAAS,MAAM,qBAAqB,KAAK,OAAO,KAAK,QAAQ;AACnE,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO;AAAA,MACnB,WAAW,KAAK,MAAM;AAAA,MACtB,WAAW,KAAK,MAAM;AAAA,IACxB;AAAA,EACF,CAAC;AAKD,QAAM,IAAsC,aAAa,OAAO,KAAK,UAAU;AAC7E,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,aAAa,IAAI,kBAAkB,IAAI;AAC7C,QAAI,CAAC,YAAY;AAGf,aAAO,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAAA,IACpC;AACA,QAAI;AACF,YAAM,WAAW,MAAM,eAAe,UAAU;AAChD,aAAO,EAAE,SAAS,GAAG,SAAS;AAAA,IAChC,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,SAAU,IAAc;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,IAGH,wBAAwB,OAAO,KAAK,UAAU;AAC/C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,MAAM,IAAI,oBAAoB,KAAK,MAAM,oBAAoB;AACnE,QAAI,aAAa,MAAM,IAAI,QAAQ;AACnC,QAAI,IAAI,MAAM,UAAU;AACtB,YAAM,MAAM,qBAAqB,UAAU,IAAI,MAAM,QAAQ;AAC7D,UAAI,CAAC,IAAI,SAAS;AAChB,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,UAC1B,OAAO;AAAA,UACP,SAAS,IAAI,MAAM,OAAO;AAAA,QAC5B,CAAC;AAAA,MACH;AACA,mBAAa,WAAW,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI;AAAA,IAC/D;AACA,QAAI,IAAI,MAAM,UAAU;AACtB,mBAAa,WAAW,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,MAAM,QAAQ;AAAA,IACzE;AACA,WAAO,EAAE,WAAW;AAAA,EACtB,CAAC;AAOD,QAAM,IAGH,wBAAwB,OAAO,KAAK,UAAU;AAC/C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,IAAI,MAAM;AACzB,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,sCAAsC,CAAC;AAAA,IAC9E;AACA,UAAM,aAAa,IAAI,kBAAkB,IAAI;AAC7C,QAAI,WAAqB,CAAC;AAC1B,QAAI,YAAY;AACd,UAAI;AACF,mBAAW,MAAM,eAAe,UAAU;AAAA,MAC5C,SAAS,KAAK;AACZ,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,UAC1B,OAAO;AAAA,UACP,SAAU,IAAc;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,aAAa,yBAAyB,KAAK,OAAO,UAAU,MAAM;AACxE,WAAO,EAAE,MAAM,QAAQ,WAAW;AAAA,EACpC,CAAC;AAED,QAAM,KAGH,mBAAmB,OAAO,KAAK,UAAU;AAC1C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,wBAAwB,UAAU,IAAI,QAAQ,CAAC,CAAC;AAC/D,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,SAAS,OAAO,MAAM,OAAO;AAAA,MAC/B,CAAC;AAAA,IACH;AAEA,UAAM,aAAa,IAAI,kBAAkB,IAAI;AAC7C,QAAI,WAAqB,CAAC;AAC1B,QAAI,YAAY;AACd,UAAI;AACF,mBAAW,MAAM,eAAe,UAAU;AAAA,MAC5C,SAAS,KAAK;AACZ,eAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,UAC1B,OAAO;AAAA,UACP,SAAU,IAAc;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AAKA,UAAM,UAAU,EAAE,KAAK,MAAM,KAAK,IAAI,EAAE;AACxC,QAAI,CAAC,OAAO,KAAK,oBAAoB;AACnC,YAAMI,cAAa,oBAAoB,KAAK,OAAO,UAAU,OAAO;AACpE,YAAMC,YAAWD,YAAW,OAAO,CAAC,MAAM,EAAE,gBAAgB,OAAO;AACnE,aAAO,EAAE,SAASC,UAAS,WAAW,GAAG,YAAAD,YAAW;AAAA,IACtD;AAMA,UAAM,aAAa,oBAAoB,KAAK,OAAO,UAAU,OAAO;AACpE,UAAM,WAAW,WAAW,OAAO,CAAC,MAAM,EAAE,gBAAgB,OAAO;AACnE,WAAO;AAAA,MACL,SAAS,SAAS,WAAW;AAAA,MAC7B,oBAAoB,OAAO,KAAK;AAAA,MAChC;AAAA,IACF;AAAA,EACF,CAAC;AAQD,QAAM,IAAsC,+BAA+B,OAAO,KAAK,UAAU;AAC/F,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACxG;AACA,QAAI;AACF,YAAM,UAAU,MAAM,mBAAmB,EAAE,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS,CAAC;AACxF,aAAO,EAAE,WAAW,QAAQ;AAAA,IAC9B,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,IAGH,kBAAkB,OAAO,KAAK,UAAU;AACzC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,UAAM,EAAE,SAAS,QAAQ,IAAI,IAAI;AACjC,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,wCAAwC,CAAC;AAAA,IAChF;AACA,UAAM,SAAS,sBAAsB,SAAS,OAAO;AACrD,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,iCAAiC,QAAQ,CAAC;AAAA,IACjF;AACA,WAAO;AAAA,EACT,CAAC;AAED,QAAM,IAAsC,oBAAoB,OAAO,KAAK,UAAU;AACpF,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACxG;AACA,QAAI;AACF,YAAM,QAAQ,MAAM,+BAA+B,EAAE,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS,CAAC;AAClG,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,KAGH,iBAAiB,OAAO,KAAK,UAAU;AACxC,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACxG;AACA,UAAM,EAAE,SAAS,yBAAyB,SAAS,qBAAqB,IAAI,IAAI,QAAQ,CAAC;AACzF,QAAI,CAAC,WAAW,CAAC,2BAA2B,CAAC,WAAW,CAAC,sBAAsB;AAC7E,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,oFAAoF,CAAC;AAAA,IAC5H;AACA,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,EAAE,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS;AAAA,QAC9C,EAAE,SAAS,yBAAyB,SAAS,qBAAqB;AAAA,MACpE;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,KAGH,mBAAmB,OAAO,KAAK,UAAU;AAC1C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACxG;AACA,UAAM,EAAE,SAAS,yBAAyB,SAAS,qBAAqB,IAAI,IAAI,QAAQ,CAAC;AACzF,QAAI,CAAC,WAAW,CAAC,2BAA2B,CAAC,WAAW,CAAC,sBAAsB;AAC7E,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,oFAAoF,CAAC;AAAA,IAC5H;AACA,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,EAAE,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS;AAAA,QAC9C,EAAE,SAAS,yBAAyB,SAAS,qBAAqB;AAAA,MACpE;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,KAGH,oBAAoB,OAAO,KAAK,UAAU;AAC3C,UAAM,OAAO,eAAe,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,aAAa;AAClF,QAAI,CAAC,KAAM;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,6CAA6C,SAAS,KAAK,KAAK,CAAC;AAAA,IACxG;AACA,UAAM,EAAE,QAAQ,IAAI,IAAI,QAAQ,CAAC;AACjC,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,4BAA4B,CAAC;AAAA,IACpE;AACA,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,EAAE,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS;AAAA,QAC9C,EAAE,QAAQ;AAAA,MACZ;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,SAAS,MAAiD;AAC9E,QAAM,MAAM,QAAQ,EAAE,QAAQ,MAAM,CAAC;AACrC,QAAM,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;AAWzC,QAAM,MAAM,YAAY;AACxB,QAAM,YAAY,KAAK,aAAa,IAAI;AACxC,QAAM,aAAa,KAAK,cAAc,IAAI;AAC1C,QAAM,aAAa,KAAK,cAAc,IAAI;AAM1C,kBAAgB,KAAK;AAAA,IACnB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EACF,CAAC;AAMD,MAAI,IAAI,eAAe,aAAa;AAAA,IAClC,YAAY,eAAe;AAAA,IAC3B,WAAW,eAAe;AAAA,EAC5B,EAAE;AAEF,QAAM,YAAY,KAAK,aAAa,KAAK,IAAI;AAC7C,QAAM,WAAW,oBAAoB,IAAI;AAEzC,QAAM,uBAAuB,CAAC,KAAK,YAAY,KAAK,eAAe;AACnE,QAAM,sBAAsB,CAAC,KAAK,YAAY,KAAK,oBAAoB;AAEvE,QAAM,gBAAgB,CAAC,SAA6C;AAClE,QAAI,KAAK,SAAS,mBAAmB,CAAC,KAAK,UAAU;AACnD,aAAO,uBAAuB,KAAK,aAAa;AAAA,IAClD;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,QAAM,qBAAqB,CAAC,SAA6C;AACvE,QAAI,KAAK,SAAS,mBAAmB,CAAC,KAAK,UAAU;AACnD,aAAO,sBAAsB,KAAK,kBAAkB;AAAA,IACtD;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AAKA,QAAM,oBAAoB,CAAC,SAA6C;AACtE,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,WAAO,GAAG,KAAK,QAAQ;AAAA,EACzB;AAEA,QAAM,WAAyB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,KAAK;AAAA,IAChB,eAAe,KAAK,eAAe;AAAA,EACrC;AAWA,MAAI,IAAI,WAAW,YAAY;AAC7B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,gBAAgB,KAAK,WAAW,KAAK,KAAK,CAAC;AACjD,UAAM,SAAS,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC5D,UAAM,QAAQ,oBAAI,IAAY;AAAA,MAC5B,GAAG,SAAS,KAAK;AAAA,MACjB,GAAG,cAAc,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACpC,CAAC;AACD,UAAM,WAAW,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS;AAC/C,YAAM,OAAO,SAAS,IAAI,IAAI;AAC9B,YAAM,UAAU,OAAO,IAAI,IAAI;AAC/B,aAAO;AAAA,QACL;AAAA,QACA,WAAW,MAAM,MAAM,SAAS;AAAA,QAChC,WAAW,MAAM,MAAM,QAAQ;AAAA,QAC/B,GAAI,UAAU,EAAE,QAAQ,QAAQ,QAAQ,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC5E;AAAA,IACF,CAAC;AACD,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,GAAI,KAAK,gBAAgB,EAAE,SAAS,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,MACjE;AAAA,IACF;AAAA,EACF,CAAC;AASD,MAAI,IAAI,aAAa,OAAO,MAAM,UAAU;AAC1C,QAAI,KAAK,eAAe;AACtB,YAAM,QAAQ,KAAK,WAAW,OAAO,KAAK,cAAc,IAAI;AAC5D,YAAM,QAAuB;AAAA,QAC3B,MAAM,KAAK,cAAc;AAAA,QACzB,MAAM,KAAK,cAAc;AAAA,QACzB,cAAc,IAAI,KAAK,SAAS,EAAE,YAAY;AAAA,QAC9C,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,QAIZ,QAAQ,UAAU,WAAW,WAAW;AAAA,MAC1C;AACA,aAAO,CAAC,KAAK;AAAA,IACf;AACA,QAAI;AACF,aAAO,MAAM,aAAqB;AAAA,IACpC,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,SAAU,IAAc;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAKD,MAAI,IAAqC,sBAAsB,OAAO,KAAK,UAAU;AACnF,QAAI;AACF,YAAM,QAAQ,MAAM,WAAmB,IAAI,OAAO,OAAO;AACzD,UAAI,CAAC,OAAO;AACV,eAAO,MACJ,KAAK,GAAG,EACR,KAAK,EAAE,OAAO,qBAAqB,SAAS,IAAI,OAAO,QAAQ,CAAC;AAAA,MACrE;AACA,aAAO,EAAE,SAAS,MAAM;AAAA,IAC1B,SAAS,KAAK;AACZ,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,SAAU,IAAc;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAKD,iBAAe,KAAK,QAAQ;AAI5B,QAAM,IAAI;AAAA,IACR,OAAO,UAAU;AACf,qBAAe,OAAO,EAAE,GAAG,UAAU,OAAO,UAAU,CAAC;AAAA,IACzD;AAAA,IACA,EAAE,QAAQ,qBAAqB;AAAA,EACjC;AAEA,SAAO;AACT;","names":["path","walk","serviceId","fs","path","fs","path","EdgeType","NodeType","NodeType","EdgeType","frontierId","fs","path","EdgeType","NodeType","Provenance","path","NodeType","EdgeType","Provenance","NodeType","Provenance","fs","path","EdgeType","NodeType","frontierId","serviceId","Provenance","fs","path","fs","path","fs","path","fs","path","fs","path","minimatch","NodeType","serviceId","fs","path","extractedEdgeId","fs","path","path","fs","fs","path","path","fs","path","fs","minimatch","serviceId","NodeType","path","fs","NodeType","serviceId","NodeType","path","fs","fs","path","EdgeType","NodeType","Provenance","extractedEdgeId","fileId","path","walk","fs","toPosix","fileId","NodeType","extractedEdgeId","EdgeType","Provenance","path","toPosix","path","fs","EdgeType","Provenance","confidenceForExtracted","extractedEdgeId","fileId","fs","path","toPosix","snippet","fileId","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","path","EdgeType","NodeType","Provenance","databaseId","confidenceForExtracted","path","path","fs","path","fs","path","parse","fs","path","path","parse","path","parse","parse","path","parse","path","parse","path","parse","path","path","parse","path","databaseId","NodeType","toPosix","path","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","NodeType","Provenance","configId","confidenceForExtracted","walk","fs","path","configId","NodeType","toPosix","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","path","Parser","JavaScript","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","toPosix","path","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","grpcMethodId","walk","fs","path","toPosix","grpcMethodId","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","EdgeType","NodeType","Provenance","confidenceForExtracted","passesExtractedFloor","path","Parser","JavaScript","Python","EdgeType","Provenance","confidenceForExtracted","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","makePyParser","Python","path","toPosix","confidenceForExtracted","EdgeType","extractedEdgeId","Provenance","path","Parser","JavaScript","EdgeType","NodeType","Provenance","confidenceForExtracted","passesExtractedFloor","serviceId","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","walk","NodeType","serviceId","path","toPosix","confidenceForExtracted","passesExtractedFloor","EdgeType","extractedEdgeId","Provenance","path","infraId","infraId","path","path","infraId","infraId","path","path","infraId","findAll","infraId","path","path","infraId","infraId","path","path","infraId","readImports","infraId","path","EdgeType","NodeType","confidenceForExtracted","toPosix","passesExtractedFloor","extractedEdgeId","Provenance","path","EdgeType","Provenance","confidenceForExtracted","NodeType","infraId","path","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","path","fs","EdgeType","Provenance","confidenceForExtracted","path","fs","toPosix","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","Provenance","confidenceForExtracted","fs","path","lineAt","toPosix","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","fs","path","parseAllDocuments","walkYamlFiles","fs","path","parseAllDocuments","path","existsSync","path","NodeType","Provenance","path","databaseId","EdgeType","NodeType","Provenance","Provenance","NodeType","EdgeType","databaseId","fs","path","Provenance","observedEdgeId","fs","path","path","fs","os","path","fs","path","DAY_MS","fs","path","os","fs","path","exists","fileExists","fs","path","os","snippet","violations","blocking"]}
|