alita-sdk 0.3.465__py3-none-any.whl → 0.3.486__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +83 -1
- alita_sdk/cli/agent_loader.py +6 -9
- alita_sdk/cli/agent_ui.py +13 -3
- alita_sdk/cli/agents.py +1866 -185
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +151 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +167 -4
- alita_sdk/cli/inventory.py +1256 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +8 -1
- alita_sdk/cli/tools/filesystem.py +815 -55
- alita_sdk/cli/tools/planning.py +143 -157
- alita_sdk/cli/tools/terminal.py +154 -20
- alita_sdk/community/__init__.py +64 -8
- alita_sdk/community/inventory/__init__.py +224 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +169 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/runtime/clients/client.py +84 -26
- alita_sdk/runtime/langchain/assistant.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +122 -31
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/toolkits/__init__.py +2 -0
- alita_sdk/runtime/toolkits/application.py +1 -1
- alita_sdk/runtime/toolkits/mcp.py +46 -36
- alita_sdk/runtime/toolkits/planning.py +171 -0
- alita_sdk/runtime/toolkits/tools.py +39 -6
- alita_sdk/runtime/tools/llm.py +185 -8
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/vectorstore_base.py +41 -6
- alita_sdk/runtime/utils/mcp_oauth.py +80 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +19 -4
- alita_sdk/tools/__init__.py +54 -27
- alita_sdk/tools/ado/repos/repos_wrapper.py +1 -2
- alita_sdk/tools/base_indexer_toolkit.py +98 -19
- alita_sdk/tools/bitbucket/__init__.py +2 -2
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +95 -6
- alita_sdk/tools/chunkers/universal_chunker.py +269 -0
- alita_sdk/tools/code_indexer_toolkit.py +55 -22
- alita_sdk/tools/elitea_base.py +86 -21
- alita_sdk/tools/jira/__init__.py +1 -1
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/qtest/__init__.py +1 -1
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +8 -2
- alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/METADATA +2 -1
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/RECORD +90 -50
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Markdown/RST document parser for extracting references and links.
|
|
3
|
+
|
|
4
|
+
Unlike code parsers that extract symbols (functions, classes), document parsers
|
|
5
|
+
extract references and links from text content.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import List, Optional, Dict, Tuple, Set
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
BaseParser, Symbol, Relationship, ParseResult,
|
|
14
|
+
RelationshipType, Range
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MarkdownParser(BaseParser):
|
|
19
|
+
"""
|
|
20
|
+
Parser for Markdown, RST, and plain text documents.
|
|
21
|
+
|
|
22
|
+
Extracts:
|
|
23
|
+
- Markdown links [text](url)
|
|
24
|
+
- Wiki-style links [[Page]]
|
|
25
|
+
- Image references
|
|
26
|
+
- RST cross-references
|
|
27
|
+
- ADR/RFC references
|
|
28
|
+
- File path references
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
language = "markdown"
|
|
32
|
+
file_extensions = ['.md', '.markdown', '.mdx', '.rst', '.txt']
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
"""Initialize the Markdown parser."""
|
|
36
|
+
super().__init__(language=self.language)
|
|
37
|
+
|
|
38
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
39
|
+
"""Return supported file extensions."""
|
|
40
|
+
return {'.md', '.markdown', '.mdx', '.rst'}
|
|
41
|
+
|
|
42
|
+
# Patterns for different reference types
|
|
43
|
+
PATTERNS = {
|
|
44
|
+
# Markdown links [text](url)
|
|
45
|
+
'md_link': re.compile(r'\[([^\]]+)\]\(([^)]+)\)', re.MULTILINE),
|
|
46
|
+
|
|
47
|
+
# Wiki-style links [[Page Name]] or [[Page|Display]]
|
|
48
|
+
'wiki_link': re.compile(r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]', re.MULTILINE),
|
|
49
|
+
|
|
50
|
+
# Markdown images 
|
|
51
|
+
'md_image': re.compile(r'!\[([^\]]*)\]\(([^)]+)\)', re.MULTILINE),
|
|
52
|
+
|
|
53
|
+
# Markdown reference-style links [text][ref]
|
|
54
|
+
'md_ref_link': re.compile(r'\[([^\]]+)\]\[([^\]]+)\]', re.MULTILINE),
|
|
55
|
+
|
|
56
|
+
# Markdown reference definitions [ref]: url
|
|
57
|
+
'md_ref_def': re.compile(r'^\s*\[([^\]]+)\]:\s*(\S+)', re.MULTILINE),
|
|
58
|
+
|
|
59
|
+
# RST :doc: and :ref: references
|
|
60
|
+
'rst_doc_ref': re.compile(r':doc:`([^`]+)`', re.MULTILINE),
|
|
61
|
+
'rst_ref': re.compile(r':ref:`([^`]+)`', re.MULTILINE),
|
|
62
|
+
'rst_class_ref': re.compile(r':class:`([^`]+)`', re.MULTILINE),
|
|
63
|
+
'rst_func_ref': re.compile(r':func:`([^`]+)`', re.MULTILINE),
|
|
64
|
+
'rst_meth_ref': re.compile(r':meth:`([^`]+)`', re.MULTILINE),
|
|
65
|
+
|
|
66
|
+
# ADR references (ADR-0001)
|
|
67
|
+
'adr_ref': re.compile(r'(?:ADR|adr)[- ]?(\d{4})', re.MULTILINE),
|
|
68
|
+
|
|
69
|
+
# RFC references
|
|
70
|
+
'rfc_ref': re.compile(r'RFC[- ]?(\d+)', re.IGNORECASE),
|
|
71
|
+
|
|
72
|
+
# File path references in text
|
|
73
|
+
'file_path': re.compile(
|
|
74
|
+
r'(?:^|\s)([a-zA-Z][\w/.-]+\.(?:py|js|ts|java|go|rs|kt|cs|swift|rb|php|c|cpp|h|md|yml|yaml|json))\b',
|
|
75
|
+
re.MULTILINE
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
# Code block with file reference
|
|
79
|
+
'code_file_ref': re.compile(r'```\w*\s*(?://|#)\s*(?:file|source):\s*([^\n]+)', re.MULTILINE),
|
|
80
|
+
|
|
81
|
+
# Headings (for document structure)
|
|
82
|
+
'heading': re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE),
|
|
83
|
+
|
|
84
|
+
# RST headings (underlined)
|
|
85
|
+
'rst_heading': re.compile(r'^(.+)\n([=\-~`]+)$', re.MULTILINE),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def _make_range(self, start_line: int, end_line: int = None) -> Range:
|
|
89
|
+
"""Create a Range object."""
|
|
90
|
+
return Range(
|
|
91
|
+
start_line=start_line,
|
|
92
|
+
end_line=end_line or start_line,
|
|
93
|
+
start_col=0,
|
|
94
|
+
end_col=0
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def _make_symbol(
|
|
98
|
+
self,
|
|
99
|
+
name: str,
|
|
100
|
+
symbol_type: str,
|
|
101
|
+
line: int,
|
|
102
|
+
file_path: str,
|
|
103
|
+
scope: str = "document",
|
|
104
|
+
**kwargs
|
|
105
|
+
) -> Symbol:
|
|
106
|
+
"""Create a Symbol with proper fields."""
|
|
107
|
+
return Symbol(
|
|
108
|
+
name=name,
|
|
109
|
+
symbol_type=symbol_type,
|
|
110
|
+
scope=scope,
|
|
111
|
+
range=self._make_range(line),
|
|
112
|
+
file_path=file_path,
|
|
113
|
+
**kwargs
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _make_relationship(
|
|
117
|
+
self,
|
|
118
|
+
source: str,
|
|
119
|
+
target: str,
|
|
120
|
+
rel_type: RelationshipType,
|
|
121
|
+
file_path: str,
|
|
122
|
+
line: int
|
|
123
|
+
) -> Relationship:
|
|
124
|
+
"""Create a Relationship with proper fields."""
|
|
125
|
+
return Relationship(
|
|
126
|
+
source_symbol=source,
|
|
127
|
+
target_symbol=target,
|
|
128
|
+
relationship_type=rel_type,
|
|
129
|
+
source_file=file_path,
|
|
130
|
+
source_range=self._make_range(line),
|
|
131
|
+
confidence=0.85
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _get_line_number(self, content: str, match_start: int) -> int:
|
|
135
|
+
"""Get line number from character position."""
|
|
136
|
+
return content[:match_start].count('\n') + 1
|
|
137
|
+
|
|
138
|
+
def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
|
|
139
|
+
"""
|
|
140
|
+
Parse a markdown/RST file for references and document structure.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
file_path: Path to the file
|
|
144
|
+
content: Optional file content (read from file if not provided)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
ParseResult with symbols (headings) and relationships (references)
|
|
148
|
+
"""
|
|
149
|
+
if content is None:
|
|
150
|
+
try:
|
|
151
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
152
|
+
content = f.read()
|
|
153
|
+
except Exception:
|
|
154
|
+
return ParseResult(symbols=[], relationships=[], errors=[f"Could not read {file_path}"])
|
|
155
|
+
|
|
156
|
+
symbols: List[Symbol] = []
|
|
157
|
+
relationships: List[Relationship] = []
|
|
158
|
+
errors: List[str] = []
|
|
159
|
+
|
|
160
|
+
# Document name for source references
|
|
161
|
+
doc_name = Path(file_path).stem
|
|
162
|
+
|
|
163
|
+
# Extract headings as document structure symbols
|
|
164
|
+
self._extract_headings(content, file_path, symbols)
|
|
165
|
+
|
|
166
|
+
# Extract all references
|
|
167
|
+
self._extract_links(content, file_path, doc_name, relationships)
|
|
168
|
+
self._extract_wiki_links(content, file_path, doc_name, relationships)
|
|
169
|
+
self._extract_images(content, file_path, doc_name, relationships)
|
|
170
|
+
self._extract_rst_refs(content, file_path, doc_name, relationships)
|
|
171
|
+
self._extract_document_refs(content, file_path, doc_name, relationships)
|
|
172
|
+
self._extract_file_refs(content, file_path, doc_name, relationships)
|
|
173
|
+
|
|
174
|
+
return ParseResult(
|
|
175
|
+
symbols=symbols,
|
|
176
|
+
relationships=relationships,
|
|
177
|
+
errors=errors
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _extract_headings(self, content: str, file_path: str, symbols: List[Symbol]):
|
|
181
|
+
"""Extract headings as document structure."""
|
|
182
|
+
# Markdown headings
|
|
183
|
+
for match in self.PATTERNS['heading'].finditer(content):
|
|
184
|
+
level = len(match.group(1))
|
|
185
|
+
title = match.group(2).strip()
|
|
186
|
+
line = self._get_line_number(content, match.start())
|
|
187
|
+
|
|
188
|
+
symbols.append(self._make_symbol(
|
|
189
|
+
name=title,
|
|
190
|
+
symbol_type=f"heading_h{level}",
|
|
191
|
+
line=line,
|
|
192
|
+
file_path=file_path,
|
|
193
|
+
metadata={'level': level}
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
# RST headings
|
|
197
|
+
for match in self.PATTERNS['rst_heading'].finditer(content):
|
|
198
|
+
title = match.group(1).strip()
|
|
199
|
+
underline = match.group(2)
|
|
200
|
+
line = self._get_line_number(content, match.start())
|
|
201
|
+
|
|
202
|
+
# Determine level by underline character
|
|
203
|
+
level_map = {'=': 1, '-': 2, '~': 3, '`': 4}
|
|
204
|
+
level = level_map.get(underline[0], 2)
|
|
205
|
+
|
|
206
|
+
symbols.append(self._make_symbol(
|
|
207
|
+
name=title,
|
|
208
|
+
symbol_type=f"heading_h{level}",
|
|
209
|
+
line=line,
|
|
210
|
+
file_path=file_path,
|
|
211
|
+
metadata={'level': level, 'format': 'rst'}
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
def _extract_links(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
215
|
+
"""Extract markdown links."""
|
|
216
|
+
for match in self.PATTERNS['md_link'].finditer(content):
|
|
217
|
+
target = match.group(2)
|
|
218
|
+
line = self._get_line_number(content, match.start())
|
|
219
|
+
|
|
220
|
+
relationships.append(self._make_relationship(
|
|
221
|
+
source=doc_name,
|
|
222
|
+
target=self._normalize_target(target),
|
|
223
|
+
rel_type=RelationshipType.REFERENCES,
|
|
224
|
+
file_path=file_path,
|
|
225
|
+
line=line
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
# Reference definitions
|
|
229
|
+
for match in self.PATTERNS['md_ref_def'].finditer(content):
|
|
230
|
+
target = match.group(2)
|
|
231
|
+
line = self._get_line_number(content, match.start())
|
|
232
|
+
|
|
233
|
+
relationships.append(self._make_relationship(
|
|
234
|
+
source=doc_name,
|
|
235
|
+
target=self._normalize_target(target),
|
|
236
|
+
rel_type=RelationshipType.REFERENCES,
|
|
237
|
+
file_path=file_path,
|
|
238
|
+
line=line
|
|
239
|
+
))
|
|
240
|
+
|
|
241
|
+
def _extract_wiki_links(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
242
|
+
"""Extract wiki-style links."""
|
|
243
|
+
for match in self.PATTERNS['wiki_link'].finditer(content):
|
|
244
|
+
target = match.group(1).strip()
|
|
245
|
+
line = self._get_line_number(content, match.start())
|
|
246
|
+
|
|
247
|
+
relationships.append(self._make_relationship(
|
|
248
|
+
source=doc_name,
|
|
249
|
+
target=target,
|
|
250
|
+
rel_type=RelationshipType.REFERENCES,
|
|
251
|
+
file_path=file_path,
|
|
252
|
+
line=line
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
def _extract_images(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
256
|
+
"""Extract image references."""
|
|
257
|
+
for match in self.PATTERNS['md_image'].finditer(content):
|
|
258
|
+
target = match.group(2)
|
|
259
|
+
line = self._get_line_number(content, match.start())
|
|
260
|
+
|
|
261
|
+
relationships.append(self._make_relationship(
|
|
262
|
+
source=doc_name,
|
|
263
|
+
target=self._normalize_target(target),
|
|
264
|
+
rel_type=RelationshipType.REFERENCES,
|
|
265
|
+
file_path=file_path,
|
|
266
|
+
line=line
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
def _extract_rst_refs(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
270
|
+
"""Extract RST cross-references."""
|
|
271
|
+
rst_patterns = ['rst_doc_ref', 'rst_ref', 'rst_class_ref', 'rst_func_ref', 'rst_meth_ref']
|
|
272
|
+
|
|
273
|
+
for pattern_name in rst_patterns:
|
|
274
|
+
for match in self.PATTERNS[pattern_name].finditer(content):
|
|
275
|
+
target = match.group(1)
|
|
276
|
+
line = self._get_line_number(content, match.start())
|
|
277
|
+
|
|
278
|
+
# Clean up RST target (remove ~ prefix for short names)
|
|
279
|
+
if target.startswith('~'):
|
|
280
|
+
target = target[1:].split('.')[-1]
|
|
281
|
+
|
|
282
|
+
relationships.append(self._make_relationship(
|
|
283
|
+
source=doc_name,
|
|
284
|
+
target=target,
|
|
285
|
+
rel_type=RelationshipType.REFERENCES,
|
|
286
|
+
file_path=file_path,
|
|
287
|
+
line=line
|
|
288
|
+
))
|
|
289
|
+
|
|
290
|
+
def _extract_document_refs(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
291
|
+
"""Extract ADR, RFC, and similar document references."""
|
|
292
|
+
# ADR references
|
|
293
|
+
for match in self.PATTERNS['adr_ref'].finditer(content):
|
|
294
|
+
adr_num = match.group(1)
|
|
295
|
+
line = self._get_line_number(content, match.start())
|
|
296
|
+
|
|
297
|
+
relationships.append(self._make_relationship(
|
|
298
|
+
source=doc_name,
|
|
299
|
+
target=f"ADR-{adr_num}",
|
|
300
|
+
rel_type=RelationshipType.REFERENCES,
|
|
301
|
+
file_path=file_path,
|
|
302
|
+
line=line
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
# RFC references
|
|
306
|
+
for match in self.PATTERNS['rfc_ref'].finditer(content):
|
|
307
|
+
rfc_num = match.group(1)
|
|
308
|
+
line = self._get_line_number(content, match.start())
|
|
309
|
+
|
|
310
|
+
relationships.append(self._make_relationship(
|
|
311
|
+
source=doc_name,
|
|
312
|
+
target=f"RFC-{rfc_num}",
|
|
313
|
+
rel_type=RelationshipType.REFERENCES,
|
|
314
|
+
file_path=file_path,
|
|
315
|
+
line=line
|
|
316
|
+
))
|
|
317
|
+
|
|
318
|
+
def _extract_file_refs(self, content: str, file_path: str, doc_name: str, relationships: List[Relationship]):
|
|
319
|
+
"""Extract file path references."""
|
|
320
|
+
for match in self.PATTERNS['file_path'].finditer(content):
|
|
321
|
+
target = match.group(1)
|
|
322
|
+
line = self._get_line_number(content, match.start())
|
|
323
|
+
|
|
324
|
+
relationships.append(self._make_relationship(
|
|
325
|
+
source=doc_name,
|
|
326
|
+
target=target,
|
|
327
|
+
rel_type=RelationshipType.REFERENCES,
|
|
328
|
+
file_path=file_path,
|
|
329
|
+
line=line
|
|
330
|
+
))
|
|
331
|
+
|
|
332
|
+
# Code block file references
|
|
333
|
+
for match in self.PATTERNS['code_file_ref'].finditer(content):
|
|
334
|
+
target = match.group(1).strip()
|
|
335
|
+
line = self._get_line_number(content, match.start())
|
|
336
|
+
|
|
337
|
+
relationships.append(self._make_relationship(
|
|
338
|
+
source=doc_name,
|
|
339
|
+
target=target,
|
|
340
|
+
rel_type=RelationshipType.REFERENCES,
|
|
341
|
+
file_path=file_path,
|
|
342
|
+
line=line
|
|
343
|
+
))
|
|
344
|
+
|
|
345
|
+
def _normalize_target(self, target: str) -> str:
|
|
346
|
+
"""Normalize link target to a clean reference name."""
|
|
347
|
+
# Remove URL scheme for external links
|
|
348
|
+
if target.startswith(('http://', 'https://')):
|
|
349
|
+
return target
|
|
350
|
+
|
|
351
|
+
# Clean relative paths
|
|
352
|
+
target = target.strip()
|
|
353
|
+
if target.startswith('./'):
|
|
354
|
+
target = target[2:]
|
|
355
|
+
|
|
356
|
+
# Extract filename without extension for local files
|
|
357
|
+
if '/' in target or '.' in target:
|
|
358
|
+
path = Path(target)
|
|
359
|
+
if path.suffix in ['.md', '.html', '.rst']:
|
|
360
|
+
return path.stem
|
|
361
|
+
|
|
362
|
+
return target
|