source-kb 0.2.2__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 (228) hide show
  1. cli/__init__.py +50 -0
  2. cli/__main__.py +5 -0
  3. cli/commands/__init__.py +1 -0
  4. cli/commands/anchor_fix.py +47 -0
  5. cli/commands/diff_doc.py +52 -0
  6. cli/commands/dispatch.py +77 -0
  7. cli/commands/extract.py +72 -0
  8. cli/commands/file_list.py +74 -0
  9. cli/commands/index.py +84 -0
  10. cli/commands/lock.py +89 -0
  11. cli/commands/merge.py +60 -0
  12. cli/commands/merge_delta.py +19 -0
  13. cli/commands/metadata.py +24 -0
  14. cli/commands/pipeline.py +45 -0
  15. cli/commands/post_merge.py +43 -0
  16. cli/commands/query.py +52 -0
  17. cli/commands/render.py +101 -0
  18. cli/commands/scan_repos.py +46 -0
  19. cli/commands/setup.py +94 -0
  20. cli/commands/split.py +196 -0
  21. cli/commands/stale_files.py +98 -0
  22. cli/commands/validate.py +191 -0
  23. core/__init__.py +32 -0
  24. core/config.py +261 -0
  25. core/docs/__init__.py +7 -0
  26. core/docs/section_updater.py +286 -0
  27. core/docs/shared.py +149 -0
  28. core/git.py +294 -0
  29. core/interfaces.py +249 -0
  30. core/monitor/__init__.py +5 -0
  31. core/monitor/progress.py +83 -0
  32. core/monitor/prompt_store.py +49 -0
  33. core/paths.py +141 -0
  34. core/preset.py +237 -0
  35. core/preset_accessors.py +202 -0
  36. core/preset_classify.py +132 -0
  37. core/preset_hooks.py +129 -0
  38. core/preset_profile.py +89 -0
  39. core/prompt/__init__.py +7 -0
  40. core/prompt/__main__.py +147 -0
  41. core/prompt/content.py +320 -0
  42. core/prompt/context_manager.py +164 -0
  43. core/prompt/renderer.py +236 -0
  44. core/prompt/response_parser.py +274 -0
  45. core/prompt/templates.py +357 -0
  46. core/prompt/validate_parity.py +162 -0
  47. core/prompt/variables.py +339 -0
  48. core/rag/__init__.py +22 -0
  49. core/rag/__main__.py +136 -0
  50. core/rag/bm25_index.py +268 -0
  51. core/rag/chunker.py +273 -0
  52. core/rag/embedder.py +151 -0
  53. core/rag/indexer.py +292 -0
  54. core/rag/loader.py +89 -0
  55. core/rag/retriever.py +82 -0
  56. core/skeleton/__init__.py +11 -0
  57. core/skeleton/__main__.py +934 -0
  58. core/skeleton/anchor_fix.py +250 -0
  59. core/skeleton/classify.py +331 -0
  60. core/skeleton/cmd_anchor_fix.py +43 -0
  61. core/skeleton/cmd_diff_doc.py +44 -0
  62. core/skeleton/cmd_lock.py +87 -0
  63. core/skeleton/cmd_merge_delta.py +41 -0
  64. core/skeleton/community.py +233 -0
  65. core/skeleton/dependency_graph.py +306 -0
  66. core/skeleton/diff_doc.py +248 -0
  67. core/skeleton/dispatch.py +273 -0
  68. core/skeleton/dispatch_render.py +319 -0
  69. core/skeleton/dispatch_source.py +111 -0
  70. core/skeleton/extract.py +218 -0
  71. core/skeleton/extract_methods.py +298 -0
  72. core/skeleton/file_list.py +239 -0
  73. core/skeleton/impact.py +278 -0
  74. core/skeleton/jar_download.py +177 -0
  75. core/skeleton/jar_resolver.py +186 -0
  76. core/skeleton/loader.py +162 -0
  77. core/skeleton/merge.py +278 -0
  78. core/skeleton/merge_delta.py +229 -0
  79. core/skeleton/metadata.py +96 -0
  80. core/skeleton/metadata_builders.py +264 -0
  81. core/skeleton/module_dag.py +330 -0
  82. core/skeleton/parsers/__init__.py +71 -0
  83. core/skeleton/parsers/jqassistant.py +300 -0
  84. core/skeleton/parsers/jqassistant_cypher.py +225 -0
  85. core/skeleton/parsers/regex.py +171 -0
  86. core/skeleton/parsers/treesitter.py +324 -0
  87. core/skeleton/parsers/treesitter_java.py +284 -0
  88. core/skeleton/parsers/treesitter_multi.py +289 -0
  89. core/skeleton/pom_parser.py +299 -0
  90. core/skeleton/post_merge.py +295 -0
  91. core/skeleton/post_merge_llm.py +82 -0
  92. core/skeleton/query.py +195 -0
  93. core/skeleton/shard_context.py +177 -0
  94. core/skeleton/split.py +180 -0
  95. core/skeleton/split_cache.py +107 -0
  96. core/skeleton/split_feedback.py +174 -0
  97. core/skeleton/split_plan.py +219 -0
  98. core/skeleton/split_plan_helpers.py +305 -0
  99. core/skeleton/split_plan_llm.py +274 -0
  100. core/utils.py +135 -0
  101. core/validators/__init__.py +65 -0
  102. core/validators/__main__.py +215 -0
  103. core/validators/consistency.py +203 -0
  104. core/validators/coverage.py +171 -0
  105. core/validators/duplicates.py +76 -0
  106. core/validators/engine.py +224 -0
  107. core/validators/links.py +76 -0
  108. core/validators/sampling.py +169 -0
  109. core/validators/structure.py +144 -0
  110. engine/__init__.py +7 -0
  111. engine/assembler.py +231 -0
  112. engine/confirm.py +65 -0
  113. engine/dedup.py +106 -0
  114. engine/main.py +211 -0
  115. engine/pipeline/__init__.py +163 -0
  116. engine/pipeline/recovery.py +250 -0
  117. engine/pipeline/steps/__init__.py +23 -0
  118. engine/pipeline/steps/audit.py +220 -0
  119. engine/pipeline/steps/audit_apply.py +195 -0
  120. engine/pipeline/steps/audit_helpers.py +155 -0
  121. engine/pipeline/steps/classify_llm.py +236 -0
  122. engine/pipeline/steps/classify_prompt.py +223 -0
  123. engine/pipeline/steps/finalize.py +160 -0
  124. engine/pipeline/steps/generate.py +169 -0
  125. engine/pipeline/steps/generate_batch.py +197 -0
  126. engine/pipeline/steps/generate_recovery.py +170 -0
  127. engine/pipeline/steps/llm_plan_split.py +253 -0
  128. engine/pipeline/steps/lock.py +64 -0
  129. engine/pipeline/steps/preflight.py +237 -0
  130. engine/pipeline/steps/preflight_adjust.py +147 -0
  131. engine/pipeline/steps/pregenerate.py +130 -0
  132. engine/pipeline/steps/quality.py +81 -0
  133. engine/pipeline/steps/skeleton.py +149 -0
  134. engine/pipeline/steps/source.py +163 -0
  135. engine/pipeline/steps/sync.py +117 -0
  136. engine/pipeline/steps/sync_finalize.py +237 -0
  137. engine/pipeline/steps/sync_update.py +341 -0
  138. engine/pipelines.py +91 -0
  139. engine/runner.py +335 -0
  140. engine/strategies/__init__.py +86 -0
  141. engine/strategies/api.py +128 -0
  142. engine/strategies/delegated.py +50 -0
  143. engine/strategies/dryrun.py +25 -0
  144. engine/two_phase.py +143 -0
  145. mcp_server/__init__.py +73 -0
  146. mcp_server/__main__.py +5 -0
  147. mcp_server/tools/__init__.py +1 -0
  148. mcp_server/tools/config.py +63 -0
  149. mcp_server/tools/discovery.py +276 -0
  150. mcp_server/tools/generation.py +184 -0
  151. mcp_server/tools/planning.py +144 -0
  152. mcp_server/tools/source.py +175 -0
  153. mcp_server/tools/validation.py +140 -0
  154. mcp_server/tools/workflow.py +166 -0
  155. mcp_server/workflow_loader.py +204 -0
  156. presets/generic/audit_dimensions.md +132 -0
  157. presets/generic/doc_types.yaml +152 -0
  158. presets/generic/preset.yaml +115 -0
  159. presets/java-spring/audit_dimensions.md +228 -0
  160. presets/java-spring/audit_dimensions.yaml +203 -0
  161. presets/java-spring/doc_types.yaml +269 -0
  162. presets/java-spring/hooks.py +122 -0
  163. presets/java-spring/preset.yaml +341 -0
  164. presets/java-spring/templates/README.md +34 -0
  165. presets/java-spring/templates/audit-system.md +15 -0
  166. presets/java-spring/templates/subagent-aop.md +105 -0
  167. presets/java-spring/templates/subagent-api.md +63 -0
  168. presets/java-spring/templates/subagent-architecture.md +111 -0
  169. presets/java-spring/templates/subagent-async-events.md +107 -0
  170. presets/java-spring/templates/subagent-audit-api-contracts.md +40 -0
  171. presets/java-spring/templates/subagent-audit-architecture.md +38 -0
  172. presets/java-spring/templates/subagent-audit-business.md +40 -0
  173. presets/java-spring/templates/subagent-audit-data-models.md +40 -0
  174. presets/java-spring/templates/subagent-business.md +129 -0
  175. presets/java-spring/templates/subagent-caching.md +75 -0
  176. presets/java-spring/templates/subagent-database-access.md +114 -0
  177. presets/java-spring/templates/subagent-enum.md +75 -0
  178. presets/java-spring/templates/subagent-error-handling.md +91 -0
  179. presets/java-spring/templates/subagent-external-integrations.md +80 -0
  180. presets/java-spring/templates/subagent-index.md +122 -0
  181. presets/java-spring/templates/subagent-messaging.md +97 -0
  182. presets/java-spring/templates/subagent-model.md +88 -0
  183. presets/java-spring/templates/subagent-observability.md +91 -0
  184. presets/java-spring/templates/subagent-scheduled.md +81 -0
  185. presets/java-spring/templates/subagent-security.md +102 -0
  186. presets/java-spring/templates/subagent-structure.md +101 -0
  187. presets/java-spring/templates/subagent-sync-section.md +34 -0
  188. presets/java-spring/templates/subagent-utils.md +73 -0
  189. presets/java-spring/templates/sync-system.md +8 -0
  190. presets/java-spring/workflow-extensions.md +112 -0
  191. skills/__init__.py +1 -0
  192. skills/_shared/README.md +30 -0
  193. skills/_shared/doc-coverage-shared.md +134 -0
  194. skills/_shared/doc-quality-standard.md +1058 -0
  195. skills/_shared/doc-subagent-rules.md +762 -0
  196. skills/_shared/windows-compat.md +89 -0
  197. skills/kb-audit/SKILL.md +52 -0
  198. skills/kb-audit/rules.md +88 -0
  199. skills/kb-audit/steps/step-01-prepare.md +75 -0
  200. skills/kb-audit/steps/step-02-audit.md +96 -0
  201. skills/kb-audit/steps/step-03-verify.md +65 -0
  202. skills/kb-audit/steps/step-04-report.md +64 -0
  203. skills/kb-init/SKILL.md +146 -0
  204. skills/kb-init/rules.md +187 -0
  205. skills/kb-init/steps/step-01-scope.md +62 -0
  206. skills/kb-init/steps/step-02-source.md +410 -0
  207. skills/kb-init/steps/step-03-generate.md +307 -0
  208. skills/kb-init/steps/step-04-quality.md +92 -0
  209. skills/kb-init/steps/step-05-finalize.md +132 -0
  210. skills/kb-init/templates/core/execution-modes.md +29 -0
  211. skills/kb-init/templates/core/output-only.md +4 -0
  212. skills/kb-init/templates/core/readwrite.md +33 -0
  213. skills/kb-search/SKILL.md +138 -0
  214. skills/kb-search/rules.md +64 -0
  215. skills/kb-sync/SKILL.md +43 -0
  216. skills/kb-sync/rules.md +70 -0
  217. skills/kb-sync/scripts/rebuild_module.py +91 -0
  218. skills/kb-sync/scripts/scan_repos.py +687 -0
  219. skills/kb-sync/steps/step-01-detect.md +72 -0
  220. skills/kb-sync/steps/step-02-update.md +71 -0
  221. skills/kb-sync/steps/step-03-verify.md +47 -0
  222. skills/kb-sync/steps/step-04-finalize.md +52 -0
  223. source_kb-0.2.2.dist-info/METADATA +194 -0
  224. source_kb-0.2.2.dist-info/RECORD +228 -0
  225. source_kb-0.2.2.dist-info/WHEEL +5 -0
  226. source_kb-0.2.2.dist-info/entry_points.txt +3 -0
  227. source_kb-0.2.2.dist-info/licenses/LICENSE +21 -0
  228. source_kb-0.2.2.dist-info/top_level.txt +6 -0
@@ -0,0 +1,284 @@
1
+ """Java-specific tree-sitter extraction logic.
2
+
3
+ Handles Javadoc extraction, annotation parsing, call target analysis,
4
+ and other Java/Kotlin/C# specific AST operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from typing import Any
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Javadoc / docstring extraction
15
+ # ---------------------------------------------------------------------------
16
+
17
+
18
+ def extract_javadoc(node, lang: str) -> str | None:
19
+ """Extract the first meaningful line from JavaDoc/docstring above a node.
20
+
21
+ For Java/Kotlin/C#: extracts from /** ... */ block comments
22
+ For Python: extracts from triple-quoted docstrings
23
+
24
+ Returns the first non-annotation line (business description), or None.
25
+ """
26
+ if lang in ("java", "kotlin", "c_sharp"):
27
+ prev = node.prev_sibling
28
+ # Skip past annotations to find the comment
29
+ while prev and prev.type in ("marker_annotation", "annotation", "modifiers"):
30
+ prev = prev.prev_sibling
31
+
32
+ if prev and prev.type in ("comment", "block_comment", "line_comment"):
33
+ text = prev.text.decode("utf-8")
34
+ if "/**" in text:
35
+ lines = text.split("\n")
36
+ for line in lines:
37
+ cleaned = line.strip().lstrip("/*").strip()
38
+ if cleaned and not cleaned.startswith("@") and len(cleaned) > 3:
39
+ return cleaned[:200]
40
+ elif text.strip().startswith("//"):
41
+ cleaned = text.strip().lstrip("/").strip()
42
+ if cleaned and len(cleaned) > 3:
43
+ return cleaned[:200]
44
+
45
+ elif lang == "python":
46
+ body = node.child_by_field_name("body")
47
+ if body and body.children:
48
+ first = body.children[0]
49
+ if first.type == "expression_statement":
50
+ for child in first.children:
51
+ if child.type == "string":
52
+ text = child.text.decode("utf-8")
53
+ text = text.strip("'\"").strip()
54
+ first_line = text.split("\n")[0].strip()
55
+ if first_line:
56
+ return first_line[:200]
57
+
58
+ return None
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Annotation extraction
63
+ # ---------------------------------------------------------------------------
64
+
65
+
66
+ def get_annotation_values(node, lang: str) -> list[dict]:
67
+ """Extract annotations WITH their attribute values (structured format).
68
+
69
+ Returns list of dicts: [{"name": "@KafkaListener", "attributes": {"topics": "order-events"}}]
70
+
71
+ Used for detecting implicit dependencies (Kafka topics, Feign clients, Redis keys).
72
+ """
73
+ if lang not in ("java", "kotlin", "c_sharp"):
74
+ return []
75
+
76
+ annotations = []
77
+ anno_nodes = []
78
+
79
+ for child in node.children:
80
+ if child.type in ("modifiers",):
81
+ for sub in child.children:
82
+ if sub.type in ("marker_annotation", "annotation"):
83
+ anno_nodes.append(sub)
84
+ elif child.type in ("marker_annotation", "annotation"):
85
+ anno_nodes.append(child)
86
+
87
+ prev = node.prev_sibling
88
+ while prev and prev.type in ("marker_annotation", "annotation"):
89
+ anno_nodes.append(prev)
90
+ prev = prev.prev_sibling
91
+
92
+ for anno_node in anno_nodes:
93
+ text = anno_node.text.decode("utf-8")
94
+ name_match = re.match(r'@(\w+)', text)
95
+ if not name_match:
96
+ continue
97
+
98
+ anno_name = "@" + name_match.group(1)
99
+ attributes: dict[str, str] = {}
100
+
101
+ args_match = re.search(r'\((.+)\)', text, re.DOTALL)
102
+ if args_match:
103
+ args_text = args_match.group(1).strip()
104
+ # Key-value pairs: key = "value"
105
+ for kv in re.finditer(r'(\w+)\s*=\s*"([^"]*)"', args_text):
106
+ attributes[kv.group(1)] = kv.group(2)
107
+ # Array patterns: key = {values}
108
+ for kv in re.finditer(r'(\w+)\s*=\s*\{([^}]*)\}', args_text):
109
+ attributes[kv.group(1)] = kv.group(2).strip().strip('"')
110
+ # Single value: @Foo("value")
111
+ if not attributes:
112
+ single_match = re.match(r'^"([^"]*)"', args_text)
113
+ if single_match:
114
+ attributes["value"] = single_match.group(1)
115
+
116
+ annotations.append({"name": anno_name, "attributes": attributes})
117
+
118
+ return annotations[:10]
119
+
120
+
121
+ def parse_java_annotation(node, lang: str) -> str | dict:
122
+ """Parse a Java/Kotlin annotation node into structured format.
123
+
124
+ Simple annotations (no args): returns "@Service"
125
+ Annotations with args: returns {"name": "@Service", "attrs": {"value": "..."}}
126
+ """
127
+ if node.type == "marker_annotation":
128
+ return node.text.decode("utf-8")
129
+
130
+ name = None
131
+ attrs: dict[str, str] = {}
132
+
133
+ for child in node.children:
134
+ if child.type in ("identifier", "scoped_identifier"):
135
+ name = "@" + child.text.decode("utf-8")
136
+ elif child.type == "annotation_argument_list":
137
+ attrs = parse_annotation_args(child)
138
+
139
+ if not name:
140
+ return node.text.decode("utf-8")
141
+
142
+ if attrs:
143
+ return {"name": name, "attrs": attrs}
144
+ return name
145
+
146
+
147
+ def parse_annotation_args(args_node) -> dict[str, str]:
148
+ """Parse annotation_argument_list into key-value dict.
149
+
150
+ Handles:
151
+ @Annotation("value") → {"value": "\"value\""}
152
+ @Annotation(name = "x", url = "y") → {"name": "\"x\"", "url": "\"y\""}
153
+ """
154
+ attrs: dict[str, str] = {}
155
+
156
+ for child in args_node.children:
157
+ if child.type == "element_value_pair":
158
+ key_node = child.child_by_field_name("key")
159
+ value_node = child.child_by_field_name("value")
160
+ if key_node and value_node:
161
+ key = key_node.text.decode("utf-8")
162
+ value = value_node.text.decode("utf-8")[:200]
163
+ attrs[key] = value
164
+ elif child.type in ("string_literal", "true", "false", "number_literal",
165
+ "identifier", "field_access", "class_literal"):
166
+ attrs["value"] = child.text.decode("utf-8")[:200]
167
+ elif child.type == "element_value_array_initializer":
168
+ attrs["value"] = child.text.decode("utf-8")[:200]
169
+
170
+ return attrs
171
+
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # Call target extraction
175
+ # ---------------------------------------------------------------------------
176
+
177
+ _CALL_NOISE = frozenset({
178
+ "this", "super", "System", "Math", "String", "Integer", "Long",
179
+ "Double", "Float", "Boolean", "Byte", "Short", "Character",
180
+ "Arrays", "Collections", "Objects", "Optional", "Stream",
181
+ "List", "Map", "Set", "HashMap", "ArrayList", "HashSet",
182
+ "StringBuilder", "StringBuffer", "Thread", "Class",
183
+ "Logger", "log", "logger", "LOG", "LOGGER",
184
+ })
185
+
186
+
187
+ def extract_call_targets(root, lang: str, content: str) -> list[str]:
188
+ """Extract class-level call targets from method bodies.
189
+
190
+ Identifies receiver objects in method calls
191
+ (e.g. 'orderService.createOrder()' → 'orderService').
192
+ Returns deduplicated list of likely dependency variable names.
193
+ """
194
+ from core.skeleton.parsers.treesitter import _walk_nodes, METHOD_NODES
195
+
196
+ if lang not in ("java", "kotlin", "c_sharp"):
197
+ return []
198
+
199
+ method_node_types = METHOD_NODES.get(lang, [])
200
+ all_receivers: set[str] = set()
201
+
202
+ for node in _walk_nodes(root, method_node_types):
203
+ body = node.text.decode("utf-8") if node.text else ""
204
+ for m in re.finditer(r'\b(\w+)\.(\w+)\s*\(', body):
205
+ receiver = m.group(1)
206
+ if receiver in _CALL_NOISE:
207
+ continue
208
+ if receiver[0].isupper() and receiver not in _CALL_NOISE:
209
+ all_receivers.add(receiver)
210
+ elif receiver[0].islower() and len(receiver) > 2:
211
+ all_receivers.add(receiver)
212
+
213
+ return sorted(all_receivers)[:50]
214
+
215
+
216
+ def extract_method_calls(node, lang: str) -> list[str]:
217
+ """Extract call targets from a single method body.
218
+
219
+ Returns list of receiver names (e.g. ['orderService', 'paymentService']).
220
+ """
221
+ if lang not in ("java", "kotlin", "c_sharp"):
222
+ return []
223
+
224
+ body = node.text.decode("utf-8") if node.text else ""
225
+ if not body:
226
+ return []
227
+
228
+ receivers: set[str] = set()
229
+ for m in re.finditer(r'\b(\w+)\.(\w+)\s*\(', body):
230
+ receiver = m.group(1)
231
+ if receiver in _CALL_NOISE or len(receiver) <= 2:
232
+ continue
233
+ if receiver[0].islower() or (receiver[0].isupper() and receiver not in _CALL_NOISE):
234
+ receivers.add(receiver)
235
+
236
+ return sorted(receivers)[:20]
237
+
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Inheritance extraction
241
+ # ---------------------------------------------------------------------------
242
+
243
+
244
+ def get_extends(node, lang: str) -> str | None:
245
+ """Extract superclass."""
246
+ sc = node.child_by_field_name("superclass")
247
+ if sc:
248
+ return sc.text.decode("utf-8")
249
+ for child in node.children:
250
+ if child.type == "superclass":
251
+ return child.text.decode("utf-8").replace("extends ", "")
252
+ return None
253
+
254
+
255
+ def get_implements(node, lang: str) -> list[str] | None:
256
+ """Extract implemented interfaces."""
257
+ for child in node.children:
258
+ if child.type in ("super_interfaces", "interfaces"):
259
+ text = child.text.decode("utf-8")
260
+ text = text.replace("implements ", "")
261
+ return [i.strip() for i in text.split(",")]
262
+ return None
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Complexity and constants
267
+ # ---------------------------------------------------------------------------
268
+
269
+
270
+ def count_branches(node) -> int:
271
+ """Count branching nodes for complexity analysis."""
272
+ from core.skeleton.parsers.treesitter import _walk_nodes, BRANCH_TYPES
273
+ count = 0
274
+ for _ in _walk_nodes(node, list(BRANCH_TYPES)):
275
+ count += 1
276
+ return count
277
+
278
+
279
+ def extract_constants(node, content: str) -> list[str]:
280
+ """Extract constant references from method body (UPPER_CASE identifiers)."""
281
+ body_text = node.text.decode("utf-8") if node.text else ""
282
+ constants = set(re.findall(r'\b([A-Z][A-Z0-9_]{2,})\b', body_text))
283
+ noise = {"NULL", "TRUE", "FALSE", "NONE", "THIS", "SELF", "GET", "SET", "PUT", "POST", "DELETE"}
284
+ return sorted(constants - noise)
@@ -0,0 +1,289 @@
1
+ """Multi-language tree-sitter specializations.
2
+
3
+ Handles fallback parsing, document-based method extraction, field extraction,
4
+ and extraction orchestration for classes/methods/fields/imports.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Fallback regex-based parsing
15
+ # ---------------------------------------------------------------------------
16
+
17
+ def fallback_parse(content: str, filepath: str) -> dict[str, Any] | None:
18
+ """Fallback to simple regex parsing when tree-sitter can't handle the file."""
19
+ lines = content.splitlines()
20
+ total_lines = len(lines)
21
+ methods: list[dict[str, Any]] = []
22
+ classes: list[dict[str, Any]] = []
23
+
24
+ func_re = re.compile(
25
+ r'^\s*(?:(?:public|private|protected|static|async|export|def|func|fn|fun)\s+)*'
26
+ r'(?:function\s+)?(\w+)\s*\('
27
+ )
28
+ class_re = re.compile(
29
+ r'^\s*(?:(?:public|abstract|final|export)\s+)*'
30
+ r'(?:class|struct|interface|enum|trait|object|type)\s+(\w+)'
31
+ )
32
+ branch_re = re.compile(
33
+ r'^\s*(?:if|else|elif|switch|case|match|for|while|catch|except)\b'
34
+ )
35
+
36
+ func_starts: list[tuple[str, int]] = []
37
+ for i, line in enumerate(lines):
38
+ m = func_re.match(line)
39
+ if m:
40
+ func_starts.append((m.group(1), i))
41
+ m = class_re.match(line)
42
+ if m:
43
+ classes.append({"name": m.group(1)})
44
+
45
+ for idx, (name, start) in enumerate(func_starts):
46
+ end = func_starts[idx + 1][1] if idx + 1 < len(func_starts) else total_lines
47
+ line_count = end - start
48
+ branches = sum(1 for ln in lines[start:end] if branch_re.match(ln))
49
+
50
+ if line_count > 50 or branches > 10:
51
+ complexity = "high"
52
+ elif line_count > 25 or branches > 5:
53
+ complexity = "medium"
54
+ else:
55
+ complexity = "low"
56
+
57
+ methods.append({
58
+ "name": name, "access": "public", "line_count": line_count,
59
+ "complexity": complexity, "branches": branches,
60
+ })
61
+
62
+ if not methods and not classes:
63
+ return None
64
+ return {"file": filepath, "total_lines": total_lines, "methods": methods, "classes": classes}
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Document-based method extraction (for coverage checks)
69
+ # ---------------------------------------------------------------------------
70
+
71
+ def extract_methods_from_doc(doc_path: str | Path) -> tuple[set[str], set[str]]:
72
+ """Extract method names and class names from documentation files.
73
+
74
+ Returns (methods, classes) sets.
75
+ """
76
+ try:
77
+ content = Path(doc_path).read_text(encoding="utf-8")
78
+ except UnicodeDecodeError:
79
+ try:
80
+ content = Path(doc_path).read_text(encoding="gbk")
81
+ except Exception:
82
+ return set(), set()
83
+
84
+ methods: set[str] = set()
85
+ classes: set[str] = set()
86
+
87
+ for m in re.finditer(r'`(\w+(?:\.\w+)?)`', content):
88
+ token = m.group(1)
89
+ if "." in token:
90
+ parts = token.split(".")
91
+ classes.add(parts[0])
92
+ methods.add(parts[-1])
93
+ elif token[0].isupper():
94
+ classes.add(token)
95
+ else:
96
+ methods.add(token)
97
+
98
+ for m in re.finditer(r'^#{2,4}\s+.*?(\w+)\s*$', content, re.MULTILINE):
99
+ token = m.group(1)
100
+ if token[0].isupper():
101
+ classes.add(token)
102
+ else:
103
+ methods.add(token)
104
+
105
+ return methods, classes
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Language-specific field extraction
110
+ # ---------------------------------------------------------------------------
111
+
112
+ def get_field_name(node, lang: str) -> str | None:
113
+ """Extract field name from a field declaration."""
114
+ name_node = node.child_by_field_name("declarator") or node.child_by_field_name("name")
115
+ if name_node:
116
+ inner = name_node.child_by_field_name("name")
117
+ if inner:
118
+ return inner.text.decode("utf-8")
119
+ return name_node.text.decode("utf-8").split("=")[0].strip()
120
+ return None
121
+
122
+
123
+ def get_field_type(node, lang: str) -> str | None:
124
+ """Extract field type from a field declaration."""
125
+ type_node = node.child_by_field_name("type")
126
+ if type_node:
127
+ return type_node.text.decode("utf-8")[:100]
128
+ return None
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Extraction orchestration (classes, methods, fields, imports)
133
+ # ---------------------------------------------------------------------------
134
+
135
+ def extract_classes(root, lang: str) -> list[dict]:
136
+ """Extract class/interface/enum declarations with full metadata."""
137
+ from core.skeleton.parsers.treesitter import (
138
+ _walk_nodes, _get_name, _classify_type, _get_annotations, CLASS_NODES,
139
+ )
140
+ from core.skeleton.parsers.treesitter_java import (
141
+ extract_javadoc, get_annotation_values, get_extends, get_implements,
142
+ )
143
+ node_types = CLASS_NODES.get(lang, [])
144
+ classes = []
145
+ for node in _walk_nodes(root, node_types):
146
+ name = _get_name(node, lang)
147
+ if not name:
148
+ continue
149
+ cls: dict[str, Any] = {"name": name, "type": _classify_type(node, lang)}
150
+ annotations = _get_annotations(node, lang)
151
+ if annotations:
152
+ cls["annotations"] = annotations
153
+ anno_values = get_annotation_values(node, lang)
154
+ if anno_values:
155
+ cls["annotation_values"] = anno_values
156
+ extends = get_extends(node, lang)
157
+ if extends:
158
+ cls["extends"] = extends
159
+ implements = get_implements(node, lang)
160
+ if implements:
161
+ cls["implements"] = implements
162
+ javadoc = extract_javadoc(node, lang)
163
+ if javadoc:
164
+ cls["doc"] = javadoc
165
+ classes.append(cls)
166
+ return classes
167
+
168
+
169
+ def extract_methods(root, lang: str, content: str) -> list[dict]:
170
+ """Extract method declarations with complexity analysis."""
171
+ from core.skeleton.parsers.treesitter import (
172
+ _walk_nodes, _get_name, _get_access, _get_params,
173
+ _get_return_type, _get_annotations, METHOD_NODES,
174
+ )
175
+ from core.skeleton.parsers.treesitter_java import (
176
+ extract_javadoc, get_annotation_values, extract_constants,
177
+ extract_method_calls, count_branches,
178
+ )
179
+ node_types = METHOD_NODES.get(lang, [])
180
+ methods = []
181
+ for node in _walk_nodes(root, node_types):
182
+ name = _get_name(node, lang)
183
+ if not name:
184
+ continue
185
+ line_count = node.end_point[0] - node.start_point[0] + 1
186
+ branches = count_branches(node)
187
+ if line_count > 50 or branches > 10:
188
+ complexity = "high"
189
+ elif line_count > 25 or branches > 5:
190
+ complexity = "medium"
191
+ else:
192
+ complexity = "low"
193
+ method: dict[str, Any] = {
194
+ "name": name, "access": _get_access(node, lang),
195
+ "line_count": line_count, "complexity": complexity, "branches": branches,
196
+ }
197
+ params = _get_params(node, lang)
198
+ if params is not None:
199
+ method["params"] = params
200
+ return_type = _get_return_type(node, lang)
201
+ if return_type:
202
+ method["return_type"] = return_type
203
+ annotations = _get_annotations(node, lang)
204
+ if annotations:
205
+ method["annotations"] = annotations
206
+ anno_values = get_annotation_values(node, lang)
207
+ if anno_values:
208
+ method["annotation_values"] = anno_values
209
+ javadoc = extract_javadoc(node, lang)
210
+ if javadoc:
211
+ method["doc"] = javadoc
212
+ constants = extract_constants(node, content)
213
+ if constants:
214
+ method["constants"] = constants[:30]
215
+ method_calls = extract_method_calls(node, lang)
216
+ if method_calls:
217
+ method["calls"] = method_calls
218
+ methods.append(method)
219
+ return methods
220
+
221
+
222
+ def extract_fields(root, lang: str) -> list[dict]:
223
+ """Extract class fields/properties."""
224
+ from core.skeleton.parsers.treesitter import _walk_nodes, _get_annotations
225
+ field_types = {
226
+ "java": ["field_declaration"], "kotlin": ["property_declaration"],
227
+ "typescript": ["public_field_definition", "property_declaration"],
228
+ "c_sharp": ["field_declaration", "property_declaration"], "python": [],
229
+ }
230
+ node_types = field_types.get(lang, [])
231
+ if not node_types:
232
+ return []
233
+ fields = []
234
+ for node in _walk_nodes(root, node_types):
235
+ name = get_field_name(node, lang)
236
+ if name:
237
+ field: dict[str, Any] = {"name": name}
238
+ ftype = get_field_type(node, lang)
239
+ if ftype:
240
+ field["type"] = ftype
241
+ annotations = _get_annotations(node, lang)
242
+ if annotations:
243
+ field["annotations"] = annotations
244
+ fields.append(field)
245
+ return fields
246
+
247
+
248
+ def extract_imports(root, lang: str) -> list[str]:
249
+ """Extract import statements for all supported languages."""
250
+ from core.skeleton.parsers.treesitter import _walk_nodes
251
+ import_node_types = {
252
+ "java": ["import_declaration"], "python": ["import_statement", "import_from_statement"],
253
+ "go": ["import_declaration", "import_spec"], "typescript": ["import_statement"],
254
+ "tsx": ["import_statement"], "javascript": ["import_statement"],
255
+ "kotlin": ["import_header"], "c_sharp": ["using_directive"],
256
+ }
257
+ node_types = import_node_types.get(lang, [])
258
+ if not node_types:
259
+ return []
260
+ imports = []
261
+ for node in _walk_nodes(root, node_types):
262
+ text = node.text.decode("utf-8").strip()
263
+ if lang == "java":
264
+ m = re.search(r'import\s+(?:static\s+)?([\w.]+)', text)
265
+ if m and not m.group(1).startswith(("java.", "javax.", "sun.", "jdk.")):
266
+ imports.append(m.group(1))
267
+ elif lang == "python":
268
+ m = re.search(r'from\s+([\w.]+)\s+import', text) or re.search(r'import\s+([\w.]+)', text)
269
+ if m:
270
+ imports.append(m.group(1))
271
+ elif lang == "go":
272
+ m = re.search(r'"([^"]+)"', text)
273
+ if m:
274
+ imports.append(m.group(1))
275
+ elif lang in ("typescript", "tsx", "javascript"):
276
+ m = re.search(r'''from\s+['"]([^'"]+)['"]''', text)
277
+ if not m:
278
+ m = re.search(r'''import\s+['"]([^'"]+)['"]''', text)
279
+ if m:
280
+ imports.append(m.group(1))
281
+ elif lang == "kotlin":
282
+ m = re.search(r'import\s+([\w.]+)', text)
283
+ if m and not m.group(1).startswith(("java.", "javax.", "kotlin.")):
284
+ imports.append(m.group(1))
285
+ elif lang == "c_sharp":
286
+ m = re.search(r'using\s+(?:static\s+)?([\w.]+)', text)
287
+ if m and not m.group(1).startswith("System."):
288
+ imports.append(m.group(1))
289
+ return imports