osscodeiq 0.0.0__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 (183) hide show
  1. osscodeiq/__init__.py +0 -0
  2. osscodeiq/analyzer.py +467 -0
  3. osscodeiq/cache/__init__.py +0 -0
  4. osscodeiq/cache/hasher.py +23 -0
  5. osscodeiq/cache/store.py +300 -0
  6. osscodeiq/classifiers/__init__.py +0 -0
  7. osscodeiq/classifiers/layer_classifier.py +69 -0
  8. osscodeiq/cli.py +721 -0
  9. osscodeiq/config.py +113 -0
  10. osscodeiq/detectors/__init__.py +0 -0
  11. osscodeiq/detectors/auth/__init__.py +0 -0
  12. osscodeiq/detectors/auth/certificate_auth.py +139 -0
  13. osscodeiq/detectors/auth/ldap_auth.py +89 -0
  14. osscodeiq/detectors/auth/session_header_auth.py +120 -0
  15. osscodeiq/detectors/base.py +41 -0
  16. osscodeiq/detectors/config/__init__.py +0 -0
  17. osscodeiq/detectors/config/batch_structure.py +128 -0
  18. osscodeiq/detectors/config/cloudformation.py +183 -0
  19. osscodeiq/detectors/config/docker_compose.py +179 -0
  20. osscodeiq/detectors/config/github_actions.py +150 -0
  21. osscodeiq/detectors/config/gitlab_ci.py +216 -0
  22. osscodeiq/detectors/config/helm_chart.py +187 -0
  23. osscodeiq/detectors/config/ini_structure.py +101 -0
  24. osscodeiq/detectors/config/json_structure.py +72 -0
  25. osscodeiq/detectors/config/kubernetes.py +305 -0
  26. osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
  27. osscodeiq/detectors/config/openapi.py +194 -0
  28. osscodeiq/detectors/config/package_json.py +99 -0
  29. osscodeiq/detectors/config/properties_detector.py +108 -0
  30. osscodeiq/detectors/config/pyproject_toml.py +169 -0
  31. osscodeiq/detectors/config/sql_structure.py +155 -0
  32. osscodeiq/detectors/config/toml_structure.py +93 -0
  33. osscodeiq/detectors/config/tsconfig_json.py +105 -0
  34. osscodeiq/detectors/config/yaml_structure.py +82 -0
  35. osscodeiq/detectors/cpp/__init__.py +0 -0
  36. osscodeiq/detectors/cpp/cpp_structures.py +192 -0
  37. osscodeiq/detectors/csharp/__init__.py +0 -0
  38. osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
  39. osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
  40. osscodeiq/detectors/csharp/csharp_structures.py +317 -0
  41. osscodeiq/detectors/docs/__init__.py +0 -0
  42. osscodeiq/detectors/docs/markdown_structure.py +117 -0
  43. osscodeiq/detectors/frontend/__init__.py +0 -0
  44. osscodeiq/detectors/frontend/angular_components.py +177 -0
  45. osscodeiq/detectors/frontend/frontend_routes.py +259 -0
  46. osscodeiq/detectors/frontend/react_components.py +148 -0
  47. osscodeiq/detectors/frontend/svelte_components.py +84 -0
  48. osscodeiq/detectors/frontend/vue_components.py +150 -0
  49. osscodeiq/detectors/generic/__init__.py +1 -0
  50. osscodeiq/detectors/generic/imports_detector.py +413 -0
  51. osscodeiq/detectors/go/__init__.py +0 -0
  52. osscodeiq/detectors/go/go_orm.py +202 -0
  53. osscodeiq/detectors/go/go_structures.py +162 -0
  54. osscodeiq/detectors/go/go_web.py +157 -0
  55. osscodeiq/detectors/iac/__init__.py +0 -0
  56. osscodeiq/detectors/iac/bicep.py +135 -0
  57. osscodeiq/detectors/iac/dockerfile.py +182 -0
  58. osscodeiq/detectors/iac/terraform.py +188 -0
  59. osscodeiq/detectors/java/__init__.py +0 -0
  60. osscodeiq/detectors/java/azure_functions.py +424 -0
  61. osscodeiq/detectors/java/azure_messaging.py +350 -0
  62. osscodeiq/detectors/java/class_hierarchy.py +349 -0
  63. osscodeiq/detectors/java/config_def.py +82 -0
  64. osscodeiq/detectors/java/cosmos_db.py +105 -0
  65. osscodeiq/detectors/java/graphql_resolver.py +188 -0
  66. osscodeiq/detectors/java/grpc_service.py +142 -0
  67. osscodeiq/detectors/java/ibm_mq.py +178 -0
  68. osscodeiq/detectors/java/jaxrs.py +160 -0
  69. osscodeiq/detectors/java/jdbc.py +196 -0
  70. osscodeiq/detectors/java/jms.py +116 -0
  71. osscodeiq/detectors/java/jpa_entity.py +143 -0
  72. osscodeiq/detectors/java/kafka.py +113 -0
  73. osscodeiq/detectors/java/kafka_protocol.py +70 -0
  74. osscodeiq/detectors/java/micronaut.py +248 -0
  75. osscodeiq/detectors/java/module_deps.py +191 -0
  76. osscodeiq/detectors/java/public_api.py +206 -0
  77. osscodeiq/detectors/java/quarkus.py +176 -0
  78. osscodeiq/detectors/java/rabbitmq.py +150 -0
  79. osscodeiq/detectors/java/raw_sql.py +136 -0
  80. osscodeiq/detectors/java/repository.py +131 -0
  81. osscodeiq/detectors/java/rmi.py +129 -0
  82. osscodeiq/detectors/java/spring_events.py +117 -0
  83. osscodeiq/detectors/java/spring_rest.py +168 -0
  84. osscodeiq/detectors/java/spring_security.py +212 -0
  85. osscodeiq/detectors/java/tibco_ems.py +193 -0
  86. osscodeiq/detectors/java/websocket.py +188 -0
  87. osscodeiq/detectors/kotlin/__init__.py +0 -0
  88. osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
  89. osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
  90. osscodeiq/detectors/proto/__init__.py +0 -0
  91. osscodeiq/detectors/proto/proto_structure.py +153 -0
  92. osscodeiq/detectors/python/__init__.py +0 -0
  93. osscodeiq/detectors/python/celery_tasks.py +88 -0
  94. osscodeiq/detectors/python/django_auth.py +132 -0
  95. osscodeiq/detectors/python/django_models.py +157 -0
  96. osscodeiq/detectors/python/django_views.py +74 -0
  97. osscodeiq/detectors/python/fastapi_auth.py +143 -0
  98. osscodeiq/detectors/python/fastapi_routes.py +68 -0
  99. osscodeiq/detectors/python/flask_routes.py +67 -0
  100. osscodeiq/detectors/python/kafka_python.py +175 -0
  101. osscodeiq/detectors/python/pydantic_models.py +115 -0
  102. osscodeiq/detectors/python/python_structures.py +234 -0
  103. osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
  104. osscodeiq/detectors/registry.py +100 -0
  105. osscodeiq/detectors/rust/__init__.py +0 -0
  106. osscodeiq/detectors/rust/actix_web.py +234 -0
  107. osscodeiq/detectors/rust/rust_structures.py +174 -0
  108. osscodeiq/detectors/scala/__init__.py +0 -0
  109. osscodeiq/detectors/scala/scala_structures.py +128 -0
  110. osscodeiq/detectors/shell/__init__.py +0 -0
  111. osscodeiq/detectors/shell/bash_detector.py +127 -0
  112. osscodeiq/detectors/shell/powershell_detector.py +118 -0
  113. osscodeiq/detectors/typescript/__init__.py +0 -0
  114. osscodeiq/detectors/typescript/express_routes.py +55 -0
  115. osscodeiq/detectors/typescript/fastify_routes.py +156 -0
  116. osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
  117. osscodeiq/detectors/typescript/kafka_js.py +164 -0
  118. osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
  119. osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
  120. osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
  121. osscodeiq/detectors/typescript/passport_jwt.py +133 -0
  122. osscodeiq/detectors/typescript/prisma_orm.py +96 -0
  123. osscodeiq/detectors/typescript/remix_routes.py +160 -0
  124. osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
  125. osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
  126. osscodeiq/detectors/typescript/typescript_structures.py +185 -0
  127. osscodeiq/detectors/utils.py +49 -0
  128. osscodeiq/discovery/__init__.py +11 -0
  129. osscodeiq/discovery/change_detector.py +97 -0
  130. osscodeiq/discovery/file_discovery.py +342 -0
  131. osscodeiq/flow/__init__.py +0 -0
  132. osscodeiq/flow/engine.py +78 -0
  133. osscodeiq/flow/models.py +72 -0
  134. osscodeiq/flow/renderer.py +127 -0
  135. osscodeiq/flow/templates/interactive.html +252 -0
  136. osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
  137. osscodeiq/flow/vendor/cytoscape.min.js +32 -0
  138. osscodeiq/flow/vendor/dagre.min.js +3809 -0
  139. osscodeiq/flow/views.py +357 -0
  140. osscodeiq/graph/__init__.py +0 -0
  141. osscodeiq/graph/backend.py +52 -0
  142. osscodeiq/graph/backends/__init__.py +23 -0
  143. osscodeiq/graph/backends/kuzu.py +576 -0
  144. osscodeiq/graph/backends/networkx.py +135 -0
  145. osscodeiq/graph/backends/sqlite_backend.py +406 -0
  146. osscodeiq/graph/builder.py +297 -0
  147. osscodeiq/graph/query.py +228 -0
  148. osscodeiq/graph/store.py +183 -0
  149. osscodeiq/graph/views.py +231 -0
  150. osscodeiq/models/__init__.py +17 -0
  151. osscodeiq/models/graph.py +116 -0
  152. osscodeiq/output/__init__.py +0 -0
  153. osscodeiq/output/dot.py +171 -0
  154. osscodeiq/output/mermaid.py +160 -0
  155. osscodeiq/output/safety.py +58 -0
  156. osscodeiq/output/serializers.py +42 -0
  157. osscodeiq/parsing/__init__.py +5 -0
  158. osscodeiq/parsing/languages/__init__.py +0 -0
  159. osscodeiq/parsing/languages/base.py +23 -0
  160. osscodeiq/parsing/languages/java.py +68 -0
  161. osscodeiq/parsing/languages/python.py +57 -0
  162. osscodeiq/parsing/languages/typescript.py +95 -0
  163. osscodeiq/parsing/parser_manager.py +125 -0
  164. osscodeiq/parsing/structured/__init__.py +0 -0
  165. osscodeiq/parsing/structured/gradle_parser.py +78 -0
  166. osscodeiq/parsing/structured/json_parser.py +24 -0
  167. osscodeiq/parsing/structured/properties_parser.py +56 -0
  168. osscodeiq/parsing/structured/sql_parser.py +54 -0
  169. osscodeiq/parsing/structured/xml_parser.py +148 -0
  170. osscodeiq/parsing/structured/yaml_parser.py +38 -0
  171. osscodeiq/server/__init__.py +7 -0
  172. osscodeiq/server/app.py +53 -0
  173. osscodeiq/server/mcp_server.py +174 -0
  174. osscodeiq/server/middleware.py +16 -0
  175. osscodeiq/server/routes.py +184 -0
  176. osscodeiq/server/service.py +445 -0
  177. osscodeiq/server/templates/welcome.html +56 -0
  178. osscodeiq-0.0.0.dist-info/METADATA +30 -0
  179. osscodeiq-0.0.0.dist-info/RECORD +183 -0
  180. osscodeiq-0.0.0.dist-info/WHEEL +5 -0
  181. osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
  182. osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
  183. osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,125 @@
1
+ """Thread-safe tree-sitter parser pool manager."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import queue
7
+ from typing import TYPE_CHECKING
8
+
9
+ import tree_sitter
10
+
11
+ from osscodeiq.parsing.languages.java import JavaLanguageSupport
12
+ from osscodeiq.parsing.languages.python import PythonLanguageSupport
13
+ from osscodeiq.parsing.languages.typescript import (
14
+ JavaScriptLanguageSupport,
15
+ TypeScriptLanguageSupport,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from osscodeiq.discovery.file_discovery import DiscoveredFile
20
+ from osscodeiq.parsing.languages.base import LanguageSupport
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Default pool size per language.
25
+ _DEFAULT_POOL_SIZE = 4
26
+
27
+
28
+ class ParserManager:
29
+ """Manages a pool of tree-sitter parsers for thread-safe parsing."""
30
+
31
+ def __init__(self, pool_size: int = _DEFAULT_POOL_SIZE) -> None:
32
+ self._pool_size = pool_size
33
+ self._languages: dict[str, LanguageSupport] = {}
34
+ self._ts_languages: dict[str, tree_sitter.Language] = {}
35
+ self._pools: dict[str, queue.Queue[tree_sitter.Parser]] = {}
36
+ self._query_cache: dict[tuple[str, str], tree_sitter.Query] = {}
37
+
38
+ # Auto-register built-in languages.
39
+ self._register_builtins()
40
+
41
+ # ------------------------------------------------------------------
42
+ # Public API
43
+ # ------------------------------------------------------------------
44
+
45
+ def register_language(self, name: str, support: LanguageSupport) -> None:
46
+ """Register a language and pre-populate its parser pool."""
47
+ self._languages[name] = support
48
+ ts_lang = support.get_language()
49
+ self._ts_languages[name] = ts_lang
50
+ self._pools[name] = self._create_pool(ts_lang)
51
+
52
+ def parse_file(
53
+ self, file: DiscoveredFile, content: bytes
54
+ ) -> tree_sitter.Tree | None:
55
+ """Parse *content* using the parser for *file*'s language.
56
+
57
+ Borrows a parser from the pool and returns it afterwards, making
58
+ this method safe to call from multiple threads concurrently.
59
+ """
60
+ lang = file.language
61
+ pool = self._pools.get(lang)
62
+ if pool is None:
63
+ logger.debug("No parser registered for language %s", lang)
64
+ return None
65
+
66
+ parser = pool.get()
67
+ try:
68
+ return parser.parse(content)
69
+ finally:
70
+ pool.put(parser)
71
+
72
+ def get_query(
73
+ self, language: str, query_name: str
74
+ ) -> tree_sitter.Query | None:
75
+ """Return a compiled tree-sitter Query, cached after first build."""
76
+ cache_key = (language, query_name)
77
+ if cache_key in self._query_cache:
78
+ return self._query_cache[cache_key]
79
+
80
+ support = self._languages.get(language)
81
+ if support is None:
82
+ return None
83
+
84
+ queries = support.get_queries()
85
+ source = queries.get(query_name)
86
+ if source is None:
87
+ return None
88
+
89
+ ts_lang = self._ts_languages[language]
90
+ compiled = tree_sitter.Query(ts_lang, source)
91
+ self._query_cache[cache_key] = compiled
92
+ return compiled
93
+
94
+ # ------------------------------------------------------------------
95
+ # Internals
96
+ # ------------------------------------------------------------------
97
+
98
+ def _create_pool(
99
+ self, ts_lang: tree_sitter.Language
100
+ ) -> queue.Queue[tree_sitter.Parser]:
101
+ pool: queue.Queue[tree_sitter.Parser] = queue.Queue(
102
+ maxsize=self._pool_size
103
+ )
104
+ for _ in range(self._pool_size):
105
+ parser = tree_sitter.Parser(ts_lang)
106
+ pool.put(parser)
107
+ return pool
108
+
109
+ def _register_builtins(self) -> None:
110
+ """Register languages that ship with the package."""
111
+ builtins: list[LanguageSupport] = [
112
+ JavaLanguageSupport(), # type: ignore[list-item]
113
+ PythonLanguageSupport(), # type: ignore[list-item]
114
+ TypeScriptLanguageSupport(), # type: ignore[list-item]
115
+ JavaScriptLanguageSupport(), # type: ignore[list-item]
116
+ ]
117
+ for support in builtins:
118
+ try:
119
+ self.register_language(support.name, support)
120
+ except Exception: # noqa: BLE001
121
+ logger.warning(
122
+ "Failed to register built-in language %s",
123
+ support.name,
124
+ exc_info=True,
125
+ )
File without changes
@@ -0,0 +1,78 @@
1
+ """Regex-based Gradle build file parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ # Patterns for Gradle dependency declarations.
9
+ # Matches: implementation 'group:artifact:version'
10
+ # api "group:artifact:version"
11
+ # compile 'group:artifact:version'
12
+ # testImplementation("group:artifact:version")
13
+ _DEP_CONFIGS = (
14
+ "implementation",
15
+ "api",
16
+ "compile",
17
+ "compileOnly",
18
+ "runtimeOnly",
19
+ "testImplementation",
20
+ "testCompile",
21
+ "testRuntimeOnly",
22
+ "annotationProcessor",
23
+ "kapt",
24
+ )
25
+
26
+ _DEP_PATTERN = re.compile(
27
+ r"(?P<config>"
28
+ + "|".join(_DEP_CONFIGS)
29
+ + r")\s*[\(\s]['\"](?P<coords>[^'\"]+)['\"]",
30
+ re.MULTILINE,
31
+ )
32
+
33
+ # Plugin patterns: id 'xxx' or id("xxx")
34
+ _PLUGIN_PATTERN = re.compile(
35
+ r"""id\s*[\(\s]['"](?P<plugin>[^'"]+)['"]""",
36
+ re.MULTILINE,
37
+ )
38
+
39
+ # Group / version from the build file.
40
+ _GROUP_PATTERN = re.compile(r"""group\s*=\s*['"](?P<group>[^'"]+)['"]""")
41
+ _VERSION_PATTERN = re.compile(r"""version\s*=\s*['"](?P<version>[^'"]+)['"]""")
42
+
43
+
44
+ class GradleParser:
45
+ """Extracts dependencies and metadata from ``build.gradle`` files."""
46
+
47
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
48
+ """Parse a Gradle build file and return structured data."""
49
+ text = content.decode("utf-8", errors="replace")
50
+
51
+ dependencies: list[dict[str, str]] = []
52
+ for m in _DEP_PATTERN.finditer(text):
53
+ config = m.group("config")
54
+ coords = m.group("coords")
55
+ parts = coords.split(":")
56
+ dep: dict[str, str] = {"configuration": config, "raw": coords}
57
+ if len(parts) >= 2:
58
+ dep["group"] = parts[0]
59
+ dep["artifact"] = parts[1]
60
+ if len(parts) >= 3:
61
+ dep["version"] = parts[2]
62
+ dependencies.append(dep)
63
+
64
+ plugins: list[str] = [
65
+ m.group("plugin") for m in _PLUGIN_PATTERN.finditer(text)
66
+ ]
67
+
68
+ group_m = _GROUP_PATTERN.search(text)
69
+ version_m = _VERSION_PATTERN.search(text)
70
+
71
+ return {
72
+ "type": "gradle",
73
+ "file": file_path,
74
+ "group": group_m.group("group") if group_m else None,
75
+ "version": version_m.group("version") if version_m else None,
76
+ "plugins": plugins,
77
+ "dependencies": dependencies,
78
+ }
@@ -0,0 +1,24 @@
1
+ """JSON structured file parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+
9
+ class JsonParser:
10
+ """Parses JSON files into structured dictionaries."""
11
+
12
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
13
+ """Parse *content* as JSON and return a structured dict."""
14
+ try:
15
+ text = content.decode("utf-8", errors="replace")
16
+ data = json.loads(text)
17
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
18
+ return {"error": "invalid_json", "file": file_path, "detail": str(exc)}
19
+
20
+ return {
21
+ "type": "json",
22
+ "file": file_path,
23
+ "data": data,
24
+ }
@@ -0,0 +1,56 @@
1
+ """Java .properties file parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class PropertiesParser:
9
+ """Parses Java-style ``.properties`` files into structured dicts."""
10
+
11
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
12
+ """Parse key=value property entries.
13
+
14
+ Handles ``=`` and ``:`` separators, comment lines (``#``, ``!``),
15
+ blank lines, and continuation lines ending with ``\\``.
16
+ """
17
+ text = content.decode("utf-8", errors="replace")
18
+ properties: dict[str, str] = {}
19
+
20
+ logical_line = ""
21
+ for raw_line in text.splitlines():
22
+ # Handle continuation lines
23
+ if logical_line:
24
+ raw_line = raw_line.lstrip()
25
+ logical_line += raw_line
26
+
27
+ if logical_line.endswith("\\"):
28
+ logical_line = logical_line[:-1]
29
+ continue
30
+
31
+ line = logical_line.strip()
32
+ logical_line = ""
33
+
34
+ if not line or line.startswith("#") or line.startswith("!"):
35
+ continue
36
+
37
+ # Split on first unescaped = or :
38
+ sep_idx = -1
39
+ for i, ch in enumerate(line):
40
+ if ch in ("=", ":") and (i == 0 or line[i - 1] != "\\"):
41
+ sep_idx = i
42
+ break
43
+
44
+ if sep_idx == -1:
45
+ # Treat the whole line as a key with empty value
46
+ properties[line] = ""
47
+ else:
48
+ key = line[:sep_idx].rstrip()
49
+ value = line[sep_idx + 1 :].lstrip()
50
+ properties[key] = value
51
+
52
+ return {
53
+ "type": "properties",
54
+ "file": file_path,
55
+ "data": properties,
56
+ }
@@ -0,0 +1,54 @@
1
+ """SQL migration file parser using sqlparse."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ import sqlparse
9
+
10
+
11
+ class SqlParser:
12
+ """Parses SQL migration files (Flyway, Liquibase) to extract DDL statements."""
13
+
14
+ _TABLE_NAME_RE = re.compile(
15
+ r"(?:CREATE|ALTER|DROP)\s+TABLE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?[`\"]?(\w+)[`\"]?",
16
+ re.IGNORECASE,
17
+ )
18
+
19
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
20
+ """Parse SQL content and extract DDL information."""
21
+ text = content.decode("utf-8", errors="replace")
22
+ statements = sqlparse.parse(text)
23
+
24
+ tables_created: list[str] = []
25
+ tables_altered: list[str] = []
26
+ tables_dropped: list[str] = []
27
+ raw_statements: list[str] = []
28
+
29
+ for stmt in statements:
30
+ stmt_text = str(stmt).strip()
31
+ if not stmt_text:
32
+ continue
33
+
34
+ stmt_type = stmt.get_type()
35
+ raw_statements.append(stmt_text)
36
+
37
+ for match in self._TABLE_NAME_RE.finditer(stmt_text):
38
+ table_name = match.group(1)
39
+ upper = stmt_text.upper().lstrip()
40
+ if upper.startswith("CREATE"):
41
+ tables_created.append(table_name)
42
+ elif upper.startswith("ALTER"):
43
+ tables_altered.append(table_name)
44
+ elif upper.startswith("DROP"):
45
+ tables_dropped.append(table_name)
46
+
47
+ return {
48
+ "type": "sql",
49
+ "file": file_path,
50
+ "tables_created": tables_created,
51
+ "tables_altered": tables_altered,
52
+ "tables_dropped": tables_dropped,
53
+ "statement_count": len(raw_statements),
54
+ }
@@ -0,0 +1,148 @@
1
+ """XML structured file parser with special handling for Maven and Spring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from lxml import etree
8
+
9
+
10
+ class XmlParser:
11
+ """Parses XML files into structured dictionaries.
12
+
13
+ Provides specialised extraction for:
14
+ - Maven ``pom.xml`` (groupId, artifactId, dependencies, modules)
15
+ - Spring XML configuration (beans, component-scan)
16
+ """
17
+
18
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
19
+ """Parse *content* and return a structured dict."""
20
+ try:
21
+ parser = etree.XMLParser(resolve_entities=False, no_network=True)
22
+ root = etree.fromstring(content, parser)
23
+ except etree.XMLSyntaxError:
24
+ return {"error": "invalid_xml", "file": file_path}
25
+
26
+ file_lower = file_path.rsplit("/", 1)[-1].lower()
27
+
28
+ if file_lower == "pom.xml":
29
+ return self._parse_pom(root, file_path)
30
+
31
+ # Detect Spring XML config by namespace or root tag.
32
+ root_tag = etree.QName(root.tag).localname if root.tag else ""
33
+ ns = root.nsmap.get(None, "")
34
+ if root_tag == "beans" or "springframework" in ns:
35
+ return self._parse_spring_xml(root, file_path)
36
+
37
+ # Generic XML: return tag tree summary.
38
+ return {
39
+ "type": "xml",
40
+ "file": file_path,
41
+ "root_tag": root_tag,
42
+ "namespaces": dict(root.nsmap),
43
+ }
44
+
45
+ # ------------------------------------------------------------------
46
+ # Maven POM parsing
47
+ # ------------------------------------------------------------------
48
+
49
+ def _parse_pom(
50
+ self, root: etree._Element, file_path: str
51
+ ) -> dict[str, Any]:
52
+ ns = root.nsmap.get(None, "")
53
+ prefix = f"{{{ns}}}" if ns else ""
54
+
55
+ def _text(parent: etree._Element, tag: str) -> str | None:
56
+ el = parent.find(f"{prefix}{tag}")
57
+ return el.text.strip() if el is not None and el.text else None
58
+
59
+ group_id = _text(root, "groupId")
60
+ artifact_id = _text(root, "artifactId")
61
+ version = _text(root, "version")
62
+ packaging = _text(root, "packaging")
63
+
64
+ # Parent info
65
+ parent_el = root.find(f"{prefix}parent")
66
+ parent: dict[str, str | None] | None = None
67
+ if parent_el is not None:
68
+ parent = {
69
+ "groupId": _text(parent_el, "groupId"),
70
+ "artifactId": _text(parent_el, "artifactId"),
71
+ "version": _text(parent_el, "version"),
72
+ }
73
+ if group_id is None:
74
+ group_id = parent.get("groupId")
75
+
76
+ # Dependencies
77
+ deps: list[dict[str, str | None]] = []
78
+ deps_el = root.find(f"{prefix}dependencies")
79
+ if deps_el is not None:
80
+ for dep in deps_el.findall(f"{prefix}dependency"):
81
+ deps.append(
82
+ {
83
+ "groupId": _text(dep, "groupId"),
84
+ "artifactId": _text(dep, "artifactId"),
85
+ "version": _text(dep, "version"),
86
+ "scope": _text(dep, "scope"),
87
+ }
88
+ )
89
+
90
+ # Modules
91
+ modules: list[str] = []
92
+ modules_el = root.find(f"{prefix}modules")
93
+ if modules_el is not None:
94
+ for mod in modules_el.findall(f"{prefix}module"):
95
+ if mod.text:
96
+ modules.append(mod.text.strip())
97
+
98
+ return {
99
+ "type": "pom",
100
+ "file": file_path,
101
+ "groupId": group_id,
102
+ "artifactId": artifact_id,
103
+ "version": version,
104
+ "packaging": packaging,
105
+ "parent": parent,
106
+ "dependencies": deps,
107
+ "modules": modules,
108
+ }
109
+
110
+ # ------------------------------------------------------------------
111
+ # Spring XML config parsing
112
+ # ------------------------------------------------------------------
113
+
114
+ def _parse_spring_xml(
115
+ self, root: etree._Element, file_path: str
116
+ ) -> dict[str, Any]:
117
+ ns = root.nsmap.get(None, "")
118
+ prefix = f"{{{ns}}}" if ns else ""
119
+
120
+ beans: list[dict[str, str | None]] = []
121
+ for bean in root.iter(f"{prefix}bean"):
122
+ beans.append(
123
+ {
124
+ "id": bean.get("id"),
125
+ "class": bean.get("class"),
126
+ "scope": bean.get("scope"),
127
+ }
128
+ )
129
+
130
+ # context:component-scan
131
+ component_scans: list[str] = []
132
+ ctx_ns = None
133
+ for pfx, uri in root.nsmap.items():
134
+ if "context" in (uri or ""):
135
+ ctx_ns = uri
136
+ break
137
+ if ctx_ns:
138
+ for scan in root.iter(f"{{{ctx_ns}}}component-scan"):
139
+ pkg = scan.get("base-package")
140
+ if pkg:
141
+ component_scans.append(pkg)
142
+
143
+ return {
144
+ "type": "spring_xml",
145
+ "file": file_path,
146
+ "beans": beans,
147
+ "component_scans": component_scans,
148
+ }
@@ -0,0 +1,38 @@
1
+ """YAML structured file parser with multi-document support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+
10
+ class YamlParser:
11
+ """Parses YAML files into structured dictionaries."""
12
+
13
+ def parse(self, content: bytes, file_path: str) -> dict[str, Any]:
14
+ """Parse *content* as YAML.
15
+
16
+ Supports multi-document YAML files (``---`` separators). When
17
+ multiple documents are present the result contains a ``documents``
18
+ list; single-document files return the document directly under a
19
+ ``data`` key.
20
+ """
21
+ try:
22
+ text = content.decode("utf-8", errors="replace")
23
+ docs = list(yaml.safe_load_all(text))
24
+ except yaml.YAMLError as exc:
25
+ return {"error": "invalid_yaml", "file": file_path, "detail": str(exc)}
26
+
27
+ if len(docs) == 1:
28
+ return {
29
+ "type": "yaml",
30
+ "file": file_path,
31
+ "data": docs[0],
32
+ }
33
+
34
+ return {
35
+ "type": "yaml_multi",
36
+ "file": file_path,
37
+ "documents": docs,
38
+ }
@@ -0,0 +1,7 @@
1
+ """OSSCodeIQ server — unified REST API + MCP on a single port."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from osscodeiq.server.app import create_app
6
+
7
+ __all__ = ["create_app"]
@@ -0,0 +1,53 @@
1
+ """FastAPI application assembly — mounts REST API, MCP server, and welcome page."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from fastapi import FastAPI
8
+ from fastapi.responses import HTMLResponse
9
+
10
+ from osscodeiq.server.middleware import AuthMiddleware
11
+ from osscodeiq.server.mcp_server import get_mcp_app, set_service
12
+ from osscodeiq.server.routes import create_router
13
+ from osscodeiq.server.service import CodeIQService
14
+
15
+
16
+ def create_app(
17
+ codebase_path: Path = Path("."),
18
+ backend: str = "networkx",
19
+ config_path: Path | None = None,
20
+ ) -> FastAPI:
21
+ """Create and configure the unified OSSCodeIQ server."""
22
+ service = CodeIQService(
23
+ path=codebase_path, backend=backend, config_path=config_path
24
+ )
25
+
26
+ # Set up MCP server
27
+ set_service(service)
28
+ mcp_app = get_mcp_app()
29
+
30
+ # Create FastAPI with MCP lifespan
31
+ app = FastAPI(
32
+ title="OSSCodeIQ",
33
+ description="OSSCodeIQ — graph queries, flow diagrams, and codebase analysis",
34
+ lifespan=mcp_app.lifespan,
35
+ )
36
+
37
+ # Auth middleware stub (no-op, ready for future auth)
38
+ app.add_middleware(AuthMiddleware)
39
+
40
+ # Mount MCP at /mcp (streamable HTTP)
41
+ app.mount("/mcp", mcp_app)
42
+
43
+ # Include REST routes at /api
44
+ router = create_router(service)
45
+ app.include_router(router)
46
+
47
+ # Welcome page at /
48
+ @app.get("/", response_class=HTMLResponse, include_in_schema=False)
49
+ async def welcome():
50
+ template_path = Path(__file__).parent / "templates" / "welcome.html"
51
+ return HTMLResponse(template_path.read_text(encoding="utf-8"))
52
+
53
+ return app