codegraph-engine 3.0.0__1-py3-none-any.whl

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.
Files changed (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,130 @@
1
+ """Canonical Runtime Observation & Static-Runtime Reconciliation Models (Phases 10-13, 16).
2
+
3
+ Architectural Rules:
4
+ 1. Runtime mapping is an OPTIONAL observation layer.
5
+ 2. Static and runtime evidence remain strictly distinguishable (`evidence_class="RUNTIME_OBSERVED"`).
6
+ 3. Never convert runtime evidence into static proof (`AST_VERIFIED`).
7
+ 4. Never convert static inference into runtime fact.
8
+ 5. `NOT_OBSERVED_AT_RUNTIME` means no runtime observation was ingested for the static edge;
9
+ it NEVER means the path cannot happen.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from enum import StrEnum
15
+ from typing import Any
16
+
17
+ from codegraph.security.redaction import normalize_and_redact_sql, redact_payload, redact_secrets
18
+
19
+
20
+ class ReconciliationStatus(StrEnum):
21
+ CONFIRMED_RUNTIME_PATH = "CONFIRMED_RUNTIME_PATH"
22
+ STATIC_RUNTIME_CONFLICT = "STATIC_RUNTIME_CONFLICT"
23
+ NOT_OBSERVED_AT_RUNTIME = "NOT_OBSERVED_AT_RUNTIME"
24
+ RUNTIME_ONLY_OBSERVED = "RUNTIME_ONLY_OBSERVED"
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class RuntimeEvent:
29
+ """Normalized, privacy-safe runtime observation event."""
30
+
31
+ event_id: str
32
+ trace_id: str
33
+ span_id: str
34
+ parent_span_id: str = ""
35
+ timestamp: str = ""
36
+ source_format: str = "json" # otel | json | sql_log
37
+ http_method: str = ""
38
+ route_path: str = ""
39
+ handler_symbol: str = ""
40
+ caller_symbol: str = ""
41
+ callee_symbol: str = ""
42
+ service_name: str = ""
43
+ db_operation: str = "" # SELECT | INSERT | UPDATE | DELETE | ""
44
+ db_table: str = ""
45
+ db_columns: tuple[str, ...] = ()
46
+ normalized_sql: str = ""
47
+ status_code: int | None = None
48
+ exception_type: str = ""
49
+ duration_ms: float = 0.0
50
+ evidence_class: str = "RUNTIME_OBSERVED"
51
+ runtime_generation: int = 1
52
+ metadata: dict[str, Any] = field(default_factory=dict)
53
+
54
+ def __post_init__(self) -> None:
55
+ if self.normalized_sql:
56
+ object.__setattr__(self, "normalized_sql", normalize_and_redact_sql(self.normalized_sql))
57
+ if self.metadata:
58
+ object.__setattr__(self, "metadata", redact_payload(dict(self.metadata)))
59
+
60
+ def as_dict(self) -> dict[str, Any]:
61
+ return {
62
+ "event_id": self.event_id,
63
+ "trace_id": self.trace_id,
64
+ "span_id": self.span_id,
65
+ "parent_span_id": self.parent_span_id,
66
+ "timestamp": self.timestamp,
67
+ "source_format": self.source_format,
68
+ "http_method": self.http_method,
69
+ "route_path": redact_secrets(self.route_path),
70
+ "handler_symbol": self.handler_symbol,
71
+ "caller_symbol": self.caller_symbol,
72
+ "callee_symbol": self.callee_symbol,
73
+ "service_name": self.service_name,
74
+ "db_operation": self.db_operation,
75
+ "db_table": self.db_table,
76
+ "db_columns": list(self.db_columns),
77
+ "normalized_sql": self.normalized_sql,
78
+ "status_code": self.status_code,
79
+ "exception_type": redact_secrets(self.exception_type),
80
+ "duration_ms": self.duration_ms,
81
+ "evidence_class": "RUNTIME_OBSERVED",
82
+ "runtime_generation": self.runtime_generation,
83
+ "metadata": dict(self.metadata),
84
+ }
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class RuntimeAggregatedEdge:
89
+ """Compact, deduplicated runtime edge stored in SQLite (Phase 16)."""
90
+
91
+ edge_id: str
92
+ source: str
93
+ target: str
94
+ relationship: str # HANDLED_BY | CALLS | READS_TABLE | WRITES_TABLE | READS_COLUMN | WRITES_COLUMN
95
+ operation: str = ""
96
+ evidence_class: str = "RUNTIME_OBSERVED"
97
+ observation_count: int = 1
98
+ first_seen: str = ""
99
+ last_seen: str = ""
100
+ avg_duration_ms: float = 0.0
101
+ max_duration_ms: float = 0.0
102
+ sample_trace_id: str = ""
103
+ sample_span_id: str = ""
104
+ normalized_sql: str = ""
105
+ status_code: int | None = None
106
+ exception_type: str = ""
107
+ runtime_generation: int = 1
108
+
109
+ def as_dict(self) -> dict[str, Any]:
110
+ return {
111
+ "edge_id": self.edge_id,
112
+ "source": self.source,
113
+ "target": self.target,
114
+ "relationship": self.relationship,
115
+ "operation": self.operation,
116
+ "evidence_class": "RUNTIME_OBSERVED",
117
+ "confidence": "HIGH",
118
+ "status": "RUNTIME_OBSERVED",
119
+ "observation_count": self.observation_count,
120
+ "first_seen": self.first_seen,
121
+ "last_seen": self.last_seen,
122
+ "avg_duration_ms": round(self.avg_duration_ms, 3),
123
+ "max_duration_ms": round(self.max_duration_ms, 3),
124
+ "sample_trace_id": self.sample_trace_id,
125
+ "sample_span_id": self.sample_span_id,
126
+ "normalized_sql": self.normalized_sql,
127
+ "status_code": self.status_code,
128
+ "exception_type": self.exception_type,
129
+ "runtime_generation": self.runtime_generation,
130
+ }
@@ -0,0 +1,304 @@
1
+ """Localhost Port and Process Inspector for Multi-Service Architectures.
2
+
3
+ Deterministically detects, fingerprints, and attributes running localhost services:
4
+ 1. Port <-> PID mapping via lsof / netstat / /proc.
5
+ 2. Process CWD and command-line attribution to workspace packages.
6
+ 3. Static port inference from docker-compose.yml, package.json, and .env.
7
+ 4. Framework fingerprinting (Next.js, Vite, FastAPI, NestJS, Express, PostgreSQL, MongoDB).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ import shutil
13
+ import subprocess
14
+ import time
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ServiceEndpoint:
22
+ port: int
23
+ host: str = "localhost"
24
+ pid: int | None = None
25
+ service_name: str = "unknown"
26
+ framework: str = "unknown"
27
+ process_cwd: str = ""
28
+ relative_repo_path: str = ""
29
+ command: str = ""
30
+ is_live: bool = False
31
+ attributes: dict[str, Any] = field(default_factory=dict)
32
+
33
+ def as_dict(self) -> dict[str, Any]:
34
+ return {
35
+ "port": self.port,
36
+ "host": self.host,
37
+ "pid": self.pid,
38
+ "service_name": self.service_name,
39
+ "framework": self.framework,
40
+ "process_cwd": self.process_cwd,
41
+ "relative_repo_path": self.relative_repo_path,
42
+ "command": self.command,
43
+ "is_live": self.is_live,
44
+ "attributes": dict(self.attributes),
45
+ }
46
+
47
+
48
+ def _infer_framework_from_cmd(cmd: str) -> str:
49
+ cmd_lower = cmd.lower()
50
+ if "next" in cmd_lower:
51
+ return "Next.js"
52
+ if "vite" in cmd_lower:
53
+ return "Vite"
54
+ if "uvicorn" in cmd_lower or "fastapi" in cmd_lower:
55
+ return "FastAPI"
56
+ if "nest" in cmd_lower:
57
+ return "NestJS"
58
+ if "express" in cmd_lower or "node" in cmd_lower:
59
+ return "Node/Express"
60
+ if "postgres" in cmd_lower:
61
+ return "PostgreSQL"
62
+ if "mongod" in cmd_lower or "mongo" in cmd_lower:
63
+ return "MongoDB"
64
+ if "redis" in cmd_lower:
65
+ return "Redis"
66
+ if "celery" in cmd_lower:
67
+ return "Celery"
68
+ return "GenericService"
69
+
70
+
71
+ def _infer_service_name_from_path(repo_root: Path, cwd_path: Path, framework: str) -> str:
72
+ try:
73
+ rel = cwd_path.relative_to(repo_root)
74
+ parts = rel.parts
75
+ if parts:
76
+ if parts[0] in ("apps", "packages", "services") and len(parts) > 1:
77
+ return parts[1]
78
+ return parts[-1]
79
+ except Exception:
80
+ pass
81
+ return framework.lower().replace(".", "").replace("/", "-")
82
+
83
+
84
+ def discover_static_configured_ports(repo_root: Path) -> dict[int, ServiceEndpoint]:
85
+ """Scan docker-compose, package.json, and .env files with in-memory TTL caching."""
86
+ global _STATIC_PORT_CACHE
87
+ root_key = str(repo_root)
88
+ now = time.monotonic()
89
+ if root_key in _STATIC_PORT_CACHE:
90
+ last_t, cached_map = _STATIC_PORT_CACHE[root_key]
91
+ if now - last_t < _PORT_CACHE_TTL_SEC:
92
+ return cached_map
93
+
94
+ results: dict[int, ServiceEndpoint] = {}
95
+
96
+ # 1. docker-compose.yml / compose.yaml
97
+ for compose_name in ("docker-compose.yml", "docker-compose.yaml", "compose.yaml", "compose.yml"):
98
+ compose_file = repo_root / compose_name
99
+ if compose_file.exists():
100
+ try:
101
+ content = compose_file.read_text(encoding="utf-8", errors="replace")
102
+ # match ports: - "3000:3000" or - 8000:8000
103
+ for m in re.finditer(r"""['"]?(\d{2,5}):(\d{2,5})['"]?""", content):
104
+ host_port = int(m.group(1))
105
+ results[host_port] = ServiceEndpoint(
106
+ port=host_port,
107
+ service_name=f"docker-service-{host_port}",
108
+ framework="DockerContainer",
109
+ relative_repo_path=str(compose_file.relative_to(repo_root)),
110
+ is_live=False,
111
+ )
112
+ except Exception:
113
+ pass
114
+
115
+ # 2. package.json scripts (e.g. -p 3000, PORT=4000)
116
+ for pkg_file in repo_root.glob("**/package.json"):
117
+ if "node_modules" in pkg_file.parts:
118
+ continue
119
+ try:
120
+ content = pkg_file.read_text(encoding="utf-8", errors="replace")
121
+ for m in re.finditer(r"""(?:PORT\s*=\s*|-p\s+|--port\s+)(\d{2,5})""", content):
122
+ port_num = int(m.group(1))
123
+ rel_dir = pkg_file.parent.relative_to(repo_root)
124
+ s_name = pkg_file.parent.name or "web"
125
+ results[port_num] = ServiceEndpoint(
126
+ port=port_num,
127
+ service_name=s_name,
128
+ framework="Node/Web",
129
+ relative_repo_path=str(rel_dir),
130
+ is_live=False,
131
+ )
132
+ except Exception:
133
+ pass
134
+
135
+ # 3. .env files
136
+ for env_file in (repo_root / ".env", repo_root / ".env.development", repo_root / ".env.local"):
137
+ if env_file.exists():
138
+ try:
139
+ content = env_file.read_text(encoding="utf-8", errors="replace")
140
+ for m in re.finditer(r"""^\s*(?:PORT|SERVER_PORT|API_PORT)\s*=\s*(\d{2,5})""", content, re.MULTILINE):
141
+ port_num = int(m.group(1))
142
+ results[port_num] = ServiceEndpoint(
143
+ port=port_num,
144
+ service_name="env-service",
145
+ framework="ConfiguredService",
146
+ relative_repo_path=str(env_file.relative_to(repo_root)),
147
+ is_live=False,
148
+ )
149
+ except Exception:
150
+ pass
151
+
152
+ _STATIC_PORT_CACHE[root_key] = (now, results)
153
+ return results
154
+
155
+
156
+ _PORT_CACHE: tuple[float, list[ServiceEndpoint]] = (0.0, [])
157
+ _STATIC_PORT_CACHE: dict[str, tuple[float, dict[int, ServiceEndpoint]]] = {}
158
+ _PORT_CACHE_TTL_SEC = 5.0
159
+
160
+
161
+ def discover_live_listening_ports(repo_root: Path | None = None) -> list[ServiceEndpoint]:
162
+ """Inspect active OS listening ports on localhost with 5-second TTL cache (Zero-CPU idle)."""
163
+ global _PORT_CACHE
164
+ now = time.monotonic()
165
+ last_time, cached_endpoints = _PORT_CACHE
166
+ if now - last_time < _PORT_CACHE_TTL_SEC and cached_endpoints:
167
+ return cached_endpoints
168
+
169
+ endpoints: list[ServiceEndpoint] = []
170
+ seen_ports: set[int] = set()
171
+
172
+ lsof_bin = shutil.which("lsof")
173
+ if lsof_bin:
174
+ try:
175
+ # lsof -iTCP -sTCP:LISTEN -n -P
176
+ proc = subprocess.run(
177
+ [lsof_bin, "-iTCP", "-sTCP:LISTEN", "-n", "-P"],
178
+ capture_output=True,
179
+ text=True,
180
+ timeout=3,
181
+ check=False,
182
+ )
183
+ if proc.returncode == 0:
184
+ for line in proc.stdout.splitlines()[1:]:
185
+ parts = line.split()
186
+ if len(parts) >= 9:
187
+ cmd = parts[0]
188
+ pid_str = parts[1]
189
+ addr_str = parts[8]
190
+ # address format: *:3000, 127.0.0.1:8000, [::1]:5432
191
+ m = re.search(r":(\d+)$", addr_str)
192
+ if m:
193
+ port = int(m.group(1))
194
+ if port in seen_ports:
195
+ continue
196
+ seen_ports.add(port)
197
+ pid = int(pid_str) if pid_str.isdigit() else None
198
+ cwd_str = ""
199
+ rel_repo = ""
200
+ if pid:
201
+ cwd_str = _get_process_cwd(pid)
202
+ if repo_root and cwd_str:
203
+ try:
204
+ rel_repo = str(Path(cwd_str).relative_to(repo_root))
205
+ except Exception:
206
+ rel_repo = ""
207
+ framework = _infer_framework_from_cmd(cmd)
208
+ svc_name = (
209
+ _infer_service_name_from_path(repo_root, Path(cwd_str), framework)
210
+ if repo_root and cwd_str
211
+ else cmd
212
+ )
213
+ endpoints.append(
214
+ ServiceEndpoint(
215
+ port=port,
216
+ pid=pid,
217
+ service_name=svc_name,
218
+ framework=framework,
219
+ process_cwd=cwd_str,
220
+ relative_repo_path=rel_repo,
221
+ command=cmd,
222
+ is_live=True,
223
+ )
224
+ )
225
+ except Exception:
226
+ pass
227
+
228
+ _PORT_CACHE = (now, endpoints)
229
+ return endpoints
230
+
231
+
232
+ def _get_process_cwd(pid: int) -> str:
233
+ """Get process working directory securely across macOS and Linux."""
234
+ # 1. Linux /proc/<pid>/cwd
235
+ proc_cwd = Path(f"/proc/{pid}/cwd")
236
+ if proc_cwd.exists():
237
+ try:
238
+ return str(proc_cwd.resolve())
239
+ except Exception:
240
+ pass
241
+
242
+ # 2. macOS lsof -p <pid> | grep cwd
243
+ lsof_bin = shutil.which("lsof")
244
+ if lsof_bin:
245
+ try:
246
+ proc = subprocess.run(
247
+ [lsof_bin, "-a", "-p", str(pid), "-d", "cwd", "-Fn"],
248
+ capture_output=True,
249
+ text=True,
250
+ timeout=1,
251
+ check=False,
252
+ )
253
+ for line in proc.stdout.splitlines():
254
+ if line.startswith("n"):
255
+ return line[1:].strip()
256
+ except Exception:
257
+ pass
258
+ return ""
259
+
260
+
261
+ def resolve_port_to_service(
262
+ port: int,
263
+ repo_root: Path,
264
+ live_endpoints: list[ServiceEndpoint] | None = None,
265
+ ) -> ServiceEndpoint:
266
+ """Identify the exact service, package, and framework for a given localhost port."""
267
+ if live_endpoints is None:
268
+ live_endpoints = discover_live_listening_ports(repo_root)
269
+
270
+ for ep in live_endpoints:
271
+ if ep.port == port:
272
+ return ep
273
+
274
+ static_map = discover_static_configured_ports(repo_root)
275
+ if port in static_map:
276
+ return static_map[port]
277
+
278
+ # Common standard ports fallback
279
+ standard_defaults: dict[int, tuple[str, str]] = {
280
+ 3000: ("frontend", "Next.js/React"),
281
+ 5173: ("frontend", "Vite"),
282
+ 8000: ("backend-api", "FastAPI/Django"),
283
+ 4000: ("backend-api", "NestJS/Express"),
284
+ 5000: ("backend-api", "Flask/Express"),
285
+ 5432: ("database", "PostgreSQL"),
286
+ 27017: ("database", "MongoDB"),
287
+ 6379: ("cache", "Redis"),
288
+ 3306: ("database", "MySQL"),
289
+ }
290
+ if port in standard_defaults:
291
+ s_name, fw = standard_defaults[port]
292
+ return ServiceEndpoint(
293
+ port=port,
294
+ service_name=s_name,
295
+ framework=fw,
296
+ is_live=False,
297
+ )
298
+
299
+ return ServiceEndpoint(
300
+ port=port,
301
+ service_name=f"service-{port}",
302
+ framework="Unknown",
303
+ is_live=False,
304
+ )