velune-cli 0.9.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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +208 -0
- velune/cli/autocomplete.py +80 -0
- velune/cli/banner.py +60 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +175 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +228 -0
- velune/cli/commands/config.py +224 -0
- velune/cli/commands/daemon.py +88 -0
- velune/cli/commands/doctor.py +721 -0
- velune/cli/commands/init.py +170 -0
- velune/cli/commands/mcp.py +82 -0
- velune/cli/commands/memory.py +293 -0
- velune/cli/commands/models.py +683 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +270 -0
- velune/cli/commands/setup.py +184 -0
- velune/cli/commands/workspace.py +249 -0
- velune/cli/context.py +36 -0
- velune/cli/councilmodel_ui.py +199 -0
- velune/cli/display/council_view.py +254 -0
- velune/cli/display/memory_view.py +126 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +25 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +51 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +123 -0
- velune/cli/registry.py +80 -0
- velune/cli/rendering/__init__.py +5 -0
- velune/cli/rendering/error_panel.py +79 -0
- velune/cli/rendering/markdown.py +63 -0
- velune/cli/repl.py +1855 -0
- velune/cli/session_manager.py +71 -0
- velune/cli/slash_commands.py +37 -0
- velune/cli/theme.py +8 -0
- velune/cognition/__init__.py +23 -0
- velune/cognition/agents/__init__.py +7 -0
- velune/cognition/agents/coder.py +209 -0
- velune/cognition/agents/planner.py +156 -0
- velune/cognition/agents/reviewer.py +195 -0
- velune/cognition/arbitrator.py +220 -0
- velune/cognition/architecture.py +415 -0
- velune/cognition/budget.py +65 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +217 -0
- velune/cognition/council/challenger.py +74 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +43 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +46 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +56 -0
- velune/cognition/council/planner.py +124 -0
- velune/cognition/council/reviewer.py +74 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +188 -0
- velune/cognition/council_orchestrator.py +282 -0
- velune/cognition/firewall.py +354 -0
- velune/cognition/module.py +46 -0
- velune/cognition/orchestrator.py +1205 -0
- velune/cognition/personality.py +238 -0
- velune/cognition/state.py +104 -0
- velune/cognition/style_resolver.py +64 -0
- velune/cognition/verification.py +205 -0
- velune/context/__init__.py +28 -0
- velune/context/assembler.py +240 -0
- velune/context/budget.py +97 -0
- velune/context/extractive.py +95 -0
- velune/context/prompt_adaptation.py +480 -0
- velune/context/sections.py +99 -0
- velune/context/token_counter.py +134 -0
- velune/context/utilization.py +33 -0
- velune/context/window.py +63 -0
- velune/core/__init__.py +89 -0
- velune/core/background.py +5 -0
- velune/core/config/__init__.py +37 -0
- velune/core/errors/__init__.py +90 -0
- velune/core/errors/catalog.py +188 -0
- velune/core/errors/execution.py +31 -0
- velune/core/errors/memory.py +25 -0
- velune/core/errors/orchestration.py +31 -0
- velune/core/errors/provider.py +37 -0
- velune/core/event_loop.py +35 -0
- velune/core/logging.py +83 -0
- velune/core/paths.py +165 -0
- velune/core/runtime.py +113 -0
- velune/core/startup_profiler.py +56 -0
- velune/core/task_registry.py +117 -0
- velune/core/trace.py +83 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +53 -0
- velune/core/types/context.py +42 -0
- velune/core/types/inference.py +38 -0
- velune/core/types/memory.py +42 -0
- velune/core/types/model.py +70 -0
- velune/core/types/provider.py +62 -0
- velune/core/types/repository.py +38 -0
- velune/core/types/task.py +61 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +127 -0
- velune/daemon/transport.py +179 -0
- velune/events.py +204 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +315 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +130 -0
- velune/execution/command_spec.py +165 -0
- velune/execution/diff_preview.py +197 -0
- velune/execution/executor.py +181 -0
- velune/execution/module.py +18 -0
- velune/execution/multi_diff.py +67 -0
- velune/execution/path_guard.py +74 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +89 -0
- velune/execution/sandbox.py +268 -0
- velune/execution/validator.py +115 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +192 -0
- velune/kernel/__init__.py +55 -0
- velune/kernel/bootstrap.py +125 -0
- velune/kernel/config.py +426 -0
- velune/kernel/entrypoint.py +78 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +143 -0
- velune/kernel/module.py +17 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +96 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +115 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +624 -0
- velune/memory/__init__.py +32 -0
- velune/memory/compaction.py +506 -0
- velune/memory/embedding_pipeline.py +241 -0
- velune/memory/lifecycle.py +680 -0
- velune/memory/module.py +218 -0
- velune/memory/prioritizer.py +67 -0
- velune/memory/storage/episodic_schema.sql +53 -0
- velune/memory/storage/lancedb_store.py +282 -0
- velune/memory/storage/sqlite_manager.py +369 -0
- velune/memory/storage/sqlite_pool.py +149 -0
- velune/memory/tiers/episodic.py +588 -0
- velune/memory/tiers/graph.py +378 -0
- velune/memory/tiers/lineage.py +416 -0
- velune/memory/tiers/semantic.py +475 -0
- velune/memory/tiers/working.py +168 -0
- velune/memory/vitality.py +132 -0
- velune/models/__init__.py +15 -0
- velune/models/family.py +76 -0
- velune/models/module.py +20 -0
- velune/models/probes.py +192 -0
- velune/models/profile_cache.py +84 -0
- velune/models/profiler.py +108 -0
- velune/models/registry.py +251 -0
- velune/models/scorer.py +233 -0
- velune/models/specializations.py +205 -0
- velune/orchestration/__init__.py +19 -0
- velune/orchestration/engine.py +239 -0
- velune/orchestration/module.py +15 -0
- velune/orchestration/role_assignments.py +82 -0
- velune/orchestration/schemas.py +98 -0
- velune/plugins/__init__.py +20 -0
- velune/plugins/hooks.py +50 -0
- velune/plugins/loader.py +161 -0
- velune/plugins/registry.py +56 -0
- velune/plugins/schemas.py +21 -0
- velune/providers/__init__.py +23 -0
- velune/providers/adapters/anthropic.py +257 -0
- velune/providers/adapters/fireworks.py +115 -0
- velune/providers/adapters/google.py +234 -0
- velune/providers/adapters/groq.py +151 -0
- velune/providers/adapters/huggingface.py +210 -0
- velune/providers/adapters/llamacpp.py +208 -0
- velune/providers/adapters/lmstudio.py +175 -0
- velune/providers/adapters/ollama.py +233 -0
- velune/providers/adapters/openai.py +213 -0
- velune/providers/adapters/openrouter.py +81 -0
- velune/providers/adapters/together.py +134 -0
- velune/providers/adapters/xai.py +60 -0
- velune/providers/base.py +86 -0
- velune/providers/benchmarker.py +138 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +79 -0
- velune/providers/discovery/benchmarks.py +44 -0
- velune/providers/discovery/classifier.py +69 -0
- velune/providers/discovery/fireworks.py +95 -0
- velune/providers/discovery/gguf.py +88 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +117 -0
- velune/providers/discovery/groq.py +21 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +162 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +115 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/health.py +67 -0
- velune/providers/health_monitor.py +169 -0
- velune/providers/keystore.py +142 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +229 -0
- velune/providers/module.py +51 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +220 -0
- velune/providers/router.py +255 -0
- velune/providers/task_classifier.py +288 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +33 -0
- velune/repository/analyzer.py +127 -0
- velune/repository/ast_parser.py +822 -0
- velune/repository/blast_radius.py +298 -0
- velune/repository/boundary_classifier.py +295 -0
- velune/repository/cognition.py +316 -0
- velune/repository/grapher.py +179 -0
- velune/repository/import_graph.py +263 -0
- velune/repository/incremental_indexer.py +275 -0
- velune/repository/index_state.py +96 -0
- velune/repository/indexer.py +243 -0
- velune/repository/module.py +17 -0
- velune/repository/parser.py +474 -0
- velune/repository/project_type.py +300 -0
- velune/repository/rename_journal.py +287 -0
- velune/repository/scanner.py +193 -0
- velune/repository/schemas.py +102 -0
- velune/repository/symbol_registry.py +365 -0
- velune/repository/tracker.py +252 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/cache.py +110 -0
- velune/retrieval/fast_path.py +391 -0
- velune/retrieval/graph.py +124 -0
- velune/retrieval/hybrid.py +271 -0
- velune/retrieval/keyword.py +131 -0
- velune/retrieval/module.py +26 -0
- velune/retrieval/pipeline.py +303 -0
- velune/retrieval/reranker.py +102 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/slow_path.py +364 -0
- velune/retrieval/vector.py +203 -0
- velune/telemetry/__init__.py +59 -0
- velune/telemetry/cognition.py +267 -0
- velune/telemetry/cost_estimator.py +92 -0
- velune/telemetry/debug.py +304 -0
- velune/telemetry/doctor.py +244 -0
- velune/telemetry/logging.py +286 -0
- velune/telemetry/spans.py +277 -0
- velune/telemetry/token_tracker.py +140 -0
- velune/telemetry/usage_tracker.py +340 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +87 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +116 -0
- velune/tools/code/search.py +123 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +136 -0
- velune/tools/filesystem/write.py +163 -0
- velune/tools/git/history.py +177 -0
- velune/tools/git/operations.py +122 -0
- velune/tools/git/state.py +121 -0
- velune/tools/module.py +81 -0
- velune/tools/terminal/execute.py +72 -0
- velune/tools/terminal/history.py +47 -0
- velune/tools/web/fetch.py +55 -0
- velune/tools/web/validator.py +122 -0
- velune_cli-0.9.0.dist-info/METADATA +518 -0
- velune_cli-0.9.0.dist-info/RECORD +279 -0
- velune_cli-0.9.0.dist-info/WHEEL +4 -0
- velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
- velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""Tree-sitter and AST multi-language parser with regex fallbacks.
|
|
2
|
+
|
|
3
|
+
This module provides :class:`RepositorySnapshotParser` — the synchronous
|
|
4
|
+
parser that returns :class:`~velune.repository.schemas.RepositorySymbol` /
|
|
5
|
+
:class:`~velune.repository.schemas.RepositoryEdge` objects for use by the
|
|
6
|
+
indexer, incremental indexer, and tools. It is intentionally distinct from
|
|
7
|
+
:class:`~velune.repository.ast_parser.ASTParser`, which is the async parser
|
|
8
|
+
used by :class:`~velune.repository.symbol_registry.SymbolRegistry` and
|
|
9
|
+
:class:`~velune.repository.rename_journal.RenameJournal`.
|
|
10
|
+
|
|
11
|
+
The lazy tree-sitter import pattern is preserved here: tree-sitter DLLs are
|
|
12
|
+
not loaded until the first actual ``parse()`` call, avoiding Windows Defender
|
|
13
|
+
real-time-scan startup costs.
|
|
14
|
+
|
|
15
|
+
.. deprecated alias:
|
|
16
|
+
``ASTParser`` is kept as a backwards-compatible alias for
|
|
17
|
+
``RepositorySnapshotParser`` so existing callers continue to work while
|
|
18
|
+
they are updated to use the canonical name.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import ast
|
|
22
|
+
import re
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from velune.repository.schemas import (
|
|
27
|
+
RepositoryEdge,
|
|
28
|
+
RepositoryLanguage,
|
|
29
|
+
RepositorySymbol,
|
|
30
|
+
RepositorySymbolKind,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Tree-sitter ships compiled C extensions (.pyd/.so). Importing them at module
|
|
34
|
+
# load time forces Windows to load several DLLs synchronously — each triggering
|
|
35
|
+
# a Defender real-time scan — adding seconds to *every* startup, even when no
|
|
36
|
+
# parsing is requested. We therefore defer all tree-sitter imports until the
|
|
37
|
+
# first actual parse via ``_ensure_tree_sitter``.
|
|
38
|
+
#
|
|
39
|
+
# ``HAS_TREE_SITTER`` starts ``None`` (unknown) and becomes ``True``/``False``
|
|
40
|
+
# after the first lazy load attempt. It remains importable for callers/tests
|
|
41
|
+
# that reference it, but no import cost is paid until parsing happens.
|
|
42
|
+
HAS_TREE_SITTER: bool | None = None
|
|
43
|
+
_TS_LANGUAGES: dict[str, Any] = {}
|
|
44
|
+
_TS_PARSER_CLS: Any = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _ensure_tree_sitter() -> bool:
|
|
48
|
+
"""Lazily import tree-sitter grammars on first use. Returns availability."""
|
|
49
|
+
global HAS_TREE_SITTER, _TS_PARSER_CLS
|
|
50
|
+
if HAS_TREE_SITTER is not None:
|
|
51
|
+
return HAS_TREE_SITTER
|
|
52
|
+
try:
|
|
53
|
+
import tree_sitter_go
|
|
54
|
+
import tree_sitter_python
|
|
55
|
+
import tree_sitter_rust
|
|
56
|
+
import tree_sitter_typescript
|
|
57
|
+
from tree_sitter import Language, Parser
|
|
58
|
+
|
|
59
|
+
_TS_PARSER_CLS = Parser
|
|
60
|
+
for name, factory in (
|
|
61
|
+
("python", tree_sitter_python.language),
|
|
62
|
+
("typescript", tree_sitter_typescript.language_typescript),
|
|
63
|
+
("javascript", tree_sitter_typescript.language_typescript),
|
|
64
|
+
("go", tree_sitter_go.language),
|
|
65
|
+
("rust", tree_sitter_rust.language),
|
|
66
|
+
):
|
|
67
|
+
try:
|
|
68
|
+
_TS_LANGUAGES[name] = Language(factory())
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
HAS_TREE_SITTER = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
HAS_TREE_SITTER = False
|
|
74
|
+
return HAS_TREE_SITTER
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RepositorySnapshotParser:
|
|
78
|
+
"""Multi-language AST and symbol parser with comprehensive fallbacks.
|
|
79
|
+
|
|
80
|
+
Returns :class:`~velune.repository.schemas.RepositorySymbol` /
|
|
81
|
+
:class:`~velune.repository.schemas.RepositoryEdge` pairs suitable for
|
|
82
|
+
building a :class:`~velune.repository.schemas.RepositorySnapshot`.
|
|
83
|
+
|
|
84
|
+
Uses tree-sitter when available (loaded lazily to avoid DLL startup cost),
|
|
85
|
+
falls back to Python's built-in ``ast`` module for Python files, and
|
|
86
|
+
finally falls back to regex for other languages.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self) -> None:
|
|
90
|
+
# Languages are populated lazily on first parse — see _ensure_loaded.
|
|
91
|
+
self.languages: dict[str, Any] = {}
|
|
92
|
+
self._parsers: dict[str, Any] = {}
|
|
93
|
+
self._loaded = False
|
|
94
|
+
|
|
95
|
+
def _ensure_loaded(self) -> bool:
|
|
96
|
+
"""Populate ``self.languages`` from the lazily-loaded grammar cache."""
|
|
97
|
+
if self._loaded:
|
|
98
|
+
return bool(self.languages)
|
|
99
|
+
self._loaded = True
|
|
100
|
+
if _ensure_tree_sitter():
|
|
101
|
+
self.languages = dict(_TS_LANGUAGES)
|
|
102
|
+
return bool(self.languages)
|
|
103
|
+
|
|
104
|
+
def parse(
|
|
105
|
+
self, file_path: Path, code: str
|
|
106
|
+
) -> tuple[list[RepositorySymbol], list[RepositoryEdge]]:
|
|
107
|
+
"""Parses source code from file_path, leveraging tree-sitter or fallbacks."""
|
|
108
|
+
lang = self._detect_language(file_path)
|
|
109
|
+
|
|
110
|
+
# Try tree-sitter if available (loaded lazily on first parse)
|
|
111
|
+
if self._ensure_loaded() and lang.value in self.languages:
|
|
112
|
+
try:
|
|
113
|
+
return self._parse_tree_sitter(file_path, code, lang)
|
|
114
|
+
except Exception:
|
|
115
|
+
# Fail silently and let fallbacks handle it
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
# Fallbacks
|
|
119
|
+
if lang == RepositoryLanguage.PYTHON:
|
|
120
|
+
return self._parse_python_ast(file_path, code)
|
|
121
|
+
|
|
122
|
+
return self._parse_regex(file_path, code, lang)
|
|
123
|
+
|
|
124
|
+
def _detect_language(self, file_path: Path) -> RepositoryLanguage:
|
|
125
|
+
"""Detect language from file path extension."""
|
|
126
|
+
suffix = file_path.suffix.lower()
|
|
127
|
+
mapping = {
|
|
128
|
+
".py": RepositoryLanguage.PYTHON,
|
|
129
|
+
".js": RepositoryLanguage.JAVASCRIPT,
|
|
130
|
+
".jsx": RepositoryLanguage.JAVASCRIPT,
|
|
131
|
+
".ts": RepositoryLanguage.TYPESCRIPT,
|
|
132
|
+
".tsx": RepositoryLanguage.TYPESCRIPT,
|
|
133
|
+
".go": RepositoryLanguage.GO,
|
|
134
|
+
".rs": RepositoryLanguage.RUST,
|
|
135
|
+
}
|
|
136
|
+
return mapping.get(suffix, RepositoryLanguage.UNKNOWN)
|
|
137
|
+
|
|
138
|
+
def _parse_tree_sitter(
|
|
139
|
+
self, file_path: Path, code: str, lang: RepositoryLanguage
|
|
140
|
+
) -> tuple[list[RepositorySymbol], list[RepositoryEdge]]:
|
|
141
|
+
"""Uses tree-sitter to parse the code and extract symbols and imports."""
|
|
142
|
+
if lang.value not in self._parsers:
|
|
143
|
+
self._parsers[lang.value] = _TS_PARSER_CLS(self.languages[lang.value])
|
|
144
|
+
parser = self._parsers[lang.value]
|
|
145
|
+
tree = parser.parse(bytes(code, "utf8"))
|
|
146
|
+
|
|
147
|
+
symbols: list[RepositorySymbol] = []
|
|
148
|
+
edges: list[RepositoryEdge] = []
|
|
149
|
+
file_path_str = str(file_path)
|
|
150
|
+
|
|
151
|
+
def walk(node: Any, parent_class: str | None = None) -> None:
|
|
152
|
+
node_type = node.type
|
|
153
|
+
name = ""
|
|
154
|
+
kind = RepositorySymbolKind.UNKNOWN
|
|
155
|
+
current_class = parent_class
|
|
156
|
+
|
|
157
|
+
# Python types
|
|
158
|
+
if lang == RepositoryLanguage.PYTHON:
|
|
159
|
+
if node_type == "class_definition":
|
|
160
|
+
name_node = node.child_by_field_name("name")
|
|
161
|
+
if name_node:
|
|
162
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
163
|
+
kind = RepositorySymbolKind.CLASS
|
|
164
|
+
current_class = name
|
|
165
|
+
elif node_type == "function_definition":
|
|
166
|
+
name_node = node.child_by_field_name("name")
|
|
167
|
+
if name_node:
|
|
168
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
169
|
+
kind = (
|
|
170
|
+
RepositorySymbolKind.METHOD
|
|
171
|
+
if parent_class
|
|
172
|
+
else RepositorySymbolKind.FUNCTION
|
|
173
|
+
)
|
|
174
|
+
elif node_type in ("import_statement", "import_from_statement"):
|
|
175
|
+
text = code[node.start_byte : node.end_byte]
|
|
176
|
+
for match in re.finditer(r"(?:import|from)\s+([\w.]+)", text):
|
|
177
|
+
target = match.group(1)
|
|
178
|
+
symbols.append(
|
|
179
|
+
RepositorySymbol(
|
|
180
|
+
name=target,
|
|
181
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
182
|
+
file_path=file_path_str,
|
|
183
|
+
line_start=node.start_point[0] + 1,
|
|
184
|
+
line_end=node.end_point[0] + 1,
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
edges.append(
|
|
188
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# JS/TS types
|
|
192
|
+
elif lang in (RepositoryLanguage.JAVASCRIPT, RepositoryLanguage.TYPESCRIPT):
|
|
193
|
+
if node_type == "class_declaration":
|
|
194
|
+
name_node = node.child_by_field_name("name")
|
|
195
|
+
if name_node:
|
|
196
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
197
|
+
kind = RepositorySymbolKind.CLASS
|
|
198
|
+
current_class = name
|
|
199
|
+
elif node_type in ("function_declaration", "method_definition"):
|
|
200
|
+
name_node = node.child_by_field_name("name")
|
|
201
|
+
if name_node:
|
|
202
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
203
|
+
kind = (
|
|
204
|
+
RepositorySymbolKind.METHOD
|
|
205
|
+
if parent_class
|
|
206
|
+
else RepositorySymbolKind.FUNCTION
|
|
207
|
+
)
|
|
208
|
+
elif node_type == "import_statement":
|
|
209
|
+
text = code[node.start_byte : node.end_byte]
|
|
210
|
+
match = re.search(r"from\s+['\"]([^'\"]+)['\"]", text)
|
|
211
|
+
if match:
|
|
212
|
+
target = match.group(1)
|
|
213
|
+
symbols.append(
|
|
214
|
+
RepositorySymbol(
|
|
215
|
+
name=target,
|
|
216
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
217
|
+
file_path=file_path_str,
|
|
218
|
+
line_start=node.start_point[0] + 1,
|
|
219
|
+
line_end=node.end_point[0] + 1,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
edges.append(
|
|
223
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Go types
|
|
227
|
+
elif lang == RepositoryLanguage.GO:
|
|
228
|
+
if node_type == "type_declaration":
|
|
229
|
+
text = code[node.start_byte : node.end_byte]
|
|
230
|
+
match = re.search(r"type\s+(\w+)\s+(?:struct|interface)", text)
|
|
231
|
+
if match:
|
|
232
|
+
name = match.group(1)
|
|
233
|
+
kind = RepositorySymbolKind.CLASS
|
|
234
|
+
current_class = name
|
|
235
|
+
elif node_type == "function_declaration":
|
|
236
|
+
name_node = node.child_by_field_name("name")
|
|
237
|
+
if name_node:
|
|
238
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
239
|
+
kind = RepositorySymbolKind.FUNCTION
|
|
240
|
+
elif node_type == "method_declaration":
|
|
241
|
+
name_node = node.child_by_field_name("name")
|
|
242
|
+
if name_node:
|
|
243
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
244
|
+
kind = RepositorySymbolKind.METHOD
|
|
245
|
+
elif node_type == "import_spec":
|
|
246
|
+
text = code[node.start_byte : node.end_byte]
|
|
247
|
+
match = re.search(r"['\"]([^'\"]+)['\"]", text)
|
|
248
|
+
if match:
|
|
249
|
+
target = match.group(1)
|
|
250
|
+
symbols.append(
|
|
251
|
+
RepositorySymbol(
|
|
252
|
+
name=target,
|
|
253
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
254
|
+
file_path=file_path_str,
|
|
255
|
+
line_start=node.start_point[0] + 1,
|
|
256
|
+
line_end=node.end_point[0] + 1,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
edges.append(
|
|
260
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Rust types
|
|
264
|
+
elif lang == RepositoryLanguage.RUST:
|
|
265
|
+
if node_type in ("struct_item", "impl_item"):
|
|
266
|
+
name_node = node.child_by_field_name("name")
|
|
267
|
+
if name_node:
|
|
268
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
269
|
+
kind = RepositorySymbolKind.CLASS
|
|
270
|
+
current_class = name
|
|
271
|
+
elif node_type == "function_item":
|
|
272
|
+
name_node = node.child_by_field_name("name")
|
|
273
|
+
if name_node:
|
|
274
|
+
name = code[name_node.start_byte : name_node.end_byte]
|
|
275
|
+
kind = (
|
|
276
|
+
RepositorySymbolKind.METHOD
|
|
277
|
+
if parent_class
|
|
278
|
+
else RepositorySymbolKind.FUNCTION
|
|
279
|
+
)
|
|
280
|
+
elif node_type == "use_declaration":
|
|
281
|
+
text = code[node.start_byte : node.end_byte]
|
|
282
|
+
match = re.search(r"use\s+([^;]+);", text)
|
|
283
|
+
if match:
|
|
284
|
+
target = match.group(1).strip()
|
|
285
|
+
symbols.append(
|
|
286
|
+
RepositorySymbol(
|
|
287
|
+
name=target,
|
|
288
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
289
|
+
file_path=file_path_str,
|
|
290
|
+
line_start=node.start_point[0] + 1,
|
|
291
|
+
line_end=node.end_point[0] + 1,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
edges.append(
|
|
295
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Append structured symbol if matched
|
|
299
|
+
if name and kind != RepositorySymbolKind.UNKNOWN:
|
|
300
|
+
symbols.append(
|
|
301
|
+
RepositorySymbol(
|
|
302
|
+
name=name,
|
|
303
|
+
kind=kind,
|
|
304
|
+
file_path=file_path_str,
|
|
305
|
+
line_start=node.start_point[0] + 1,
|
|
306
|
+
line_end=node.end_point[0] + 1,
|
|
307
|
+
parent=parent_class,
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Recurse children
|
|
312
|
+
for child in node.children:
|
|
313
|
+
walk(child, current_class)
|
|
314
|
+
|
|
315
|
+
walk(tree.root_node)
|
|
316
|
+
return symbols, edges
|
|
317
|
+
|
|
318
|
+
def _parse_python_ast(
|
|
319
|
+
self, file_path: Path, code: str
|
|
320
|
+
) -> tuple[list[RepositorySymbol], list[RepositoryEdge]]:
|
|
321
|
+
"""Standard Python AST library fallback."""
|
|
322
|
+
try:
|
|
323
|
+
tree = ast.parse(code)
|
|
324
|
+
except SyntaxError:
|
|
325
|
+
return [], []
|
|
326
|
+
|
|
327
|
+
symbols: list[RepositorySymbol] = []
|
|
328
|
+
edges: list[RepositoryEdge] = []
|
|
329
|
+
file_path_str = str(file_path)
|
|
330
|
+
|
|
331
|
+
class PythonVisitor(ast.NodeVisitor):
|
|
332
|
+
def __init__(self) -> None:
|
|
333
|
+
self.class_stack: list[str] = []
|
|
334
|
+
|
|
335
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
336
|
+
doc = ast.get_docstring(node)
|
|
337
|
+
symbols.append(
|
|
338
|
+
RepositorySymbol(
|
|
339
|
+
name=node.name,
|
|
340
|
+
kind=RepositorySymbolKind.CLASS,
|
|
341
|
+
file_path=file_path_str,
|
|
342
|
+
line_start=getattr(node, "lineno", 1),
|
|
343
|
+
line_end=getattr(node, "end_lineno", getattr(node, "lineno", 1)),
|
|
344
|
+
docstring=doc,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
self.class_stack.append(node.name)
|
|
348
|
+
self.generic_visit(node)
|
|
349
|
+
self.class_stack.pop()
|
|
350
|
+
|
|
351
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
352
|
+
self._visit_function(node)
|
|
353
|
+
|
|
354
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
355
|
+
self._visit_function(node)
|
|
356
|
+
|
|
357
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
358
|
+
for alias in node.names:
|
|
359
|
+
target = alias.name
|
|
360
|
+
edges.append(
|
|
361
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
362
|
+
)
|
|
363
|
+
symbols.append(
|
|
364
|
+
RepositorySymbol(
|
|
365
|
+
name=target,
|
|
366
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
367
|
+
file_path=file_path_str,
|
|
368
|
+
line_start=node.lineno,
|
|
369
|
+
line_end=getattr(node, "end_lineno", node.lineno),
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
374
|
+
mod = node.module or ""
|
|
375
|
+
for alias in node.names:
|
|
376
|
+
target = f"{mod}.{alias.name}" if mod else alias.name
|
|
377
|
+
edges.append(
|
|
378
|
+
RepositoryEdge(source=file_path_str, target=target, edge_type="imports")
|
|
379
|
+
)
|
|
380
|
+
symbols.append(
|
|
381
|
+
RepositorySymbol(
|
|
382
|
+
name=alias.name,
|
|
383
|
+
kind=RepositorySymbolKind.IMPORT,
|
|
384
|
+
file_path=file_path_str,
|
|
385
|
+
line_start=node.lineno,
|
|
386
|
+
line_end=getattr(node, "end_lineno", node.lineno),
|
|
387
|
+
metadata={"module": mod},
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def _visit_function(self, node: Any) -> None:
|
|
392
|
+
doc = ast.get_docstring(node)
|
|
393
|
+
kind = (
|
|
394
|
+
RepositorySymbolKind.METHOD
|
|
395
|
+
if self.class_stack
|
|
396
|
+
else RepositorySymbolKind.FUNCTION
|
|
397
|
+
)
|
|
398
|
+
symbols.append(
|
|
399
|
+
RepositorySymbol(
|
|
400
|
+
name=node.name,
|
|
401
|
+
kind=kind,
|
|
402
|
+
file_path=file_path_str,
|
|
403
|
+
line_start=node.lineno,
|
|
404
|
+
line_end=getattr(node, "end_lineno", node.lineno),
|
|
405
|
+
docstring=doc,
|
|
406
|
+
parent=self.class_stack[-1] if self.class_stack else None,
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
self.generic_visit(node)
|
|
410
|
+
|
|
411
|
+
PythonVisitor().visit(tree)
|
|
412
|
+
return symbols, edges
|
|
413
|
+
|
|
414
|
+
def _parse_regex(
|
|
415
|
+
self, file_path: Path, code: str, lang: RepositoryLanguage
|
|
416
|
+
) -> tuple[list[RepositorySymbol], list[RepositoryEdge]]:
|
|
417
|
+
"""Universal regex symbol extractor fallback."""
|
|
418
|
+
patterns = {
|
|
419
|
+
RepositoryLanguage.JAVASCRIPT: [
|
|
420
|
+
(r"(?:export\s+)?class\s+(\w+)", RepositorySymbolKind.CLASS),
|
|
421
|
+
(r"(?:export\s+)?function\s+(\w+)", RepositorySymbolKind.FUNCTION),
|
|
422
|
+
(r"import\s+.*?from\s+['\"]([^'\"]+)['\"]", RepositorySymbolKind.IMPORT),
|
|
423
|
+
],
|
|
424
|
+
RepositoryLanguage.TYPESCRIPT: [
|
|
425
|
+
(r"(?:export\s+)?class\s+(\w+)", RepositorySymbolKind.CLASS),
|
|
426
|
+
(r"(?:export\s+)?(?:async\s+)?function\s+(\w+)", RepositorySymbolKind.FUNCTION),
|
|
427
|
+
(r"import\s+.*?from\s+['\"]([^'\"]+)['\"]", RepositorySymbolKind.IMPORT),
|
|
428
|
+
],
|
|
429
|
+
RepositoryLanguage.GO: [
|
|
430
|
+
(r"type\s+(\w+)\s+struct", RepositorySymbolKind.CLASS),
|
|
431
|
+
(r"func\s+(\w+)", RepositorySymbolKind.FUNCTION),
|
|
432
|
+
(r"import\s+['\"]([^'\"]+)['\"]", RepositorySymbolKind.IMPORT),
|
|
433
|
+
],
|
|
434
|
+
RepositoryLanguage.RUST: [
|
|
435
|
+
(r"(?:pub\s+)?struct\s+(\w+)", RepositorySymbolKind.CLASS),
|
|
436
|
+
(r"(?:pub\s+)?(?:async\s+)?fn\s+(\w+)", RepositorySymbolKind.FUNCTION),
|
|
437
|
+
(r"use\s+([^;]+);", RepositorySymbolKind.IMPORT),
|
|
438
|
+
],
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
symbols: list[RepositorySymbol] = []
|
|
442
|
+
edges: list[RepositoryEdge] = []
|
|
443
|
+
file_path_str = str(file_path)
|
|
444
|
+
|
|
445
|
+
for pattern, kind in patterns.get(lang, []):
|
|
446
|
+
for match in re.finditer(pattern, code):
|
|
447
|
+
value = match.group(1).strip()
|
|
448
|
+
start_char = match.start()
|
|
449
|
+
line_no = code[:start_char].count("\n") + 1
|
|
450
|
+
|
|
451
|
+
if kind == RepositorySymbolKind.IMPORT:
|
|
452
|
+
edges.append(
|
|
453
|
+
RepositoryEdge(source=file_path_str, target=value, edge_type="imports")
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
symbols.append(
|
|
457
|
+
RepositorySymbol(
|
|
458
|
+
name=value,
|
|
459
|
+
kind=kind,
|
|
460
|
+
file_path=file_path_str,
|
|
461
|
+
line_start=line_no,
|
|
462
|
+
line_end=line_no,
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
return symbols, edges
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# ---------------------------------------------------------------------------
|
|
470
|
+
# Backwards-compatibility alias
|
|
471
|
+
# Callers that import ``from velune.repository.parser import ASTParser`` will
|
|
472
|
+
# continue to work while they migrate to ``RepositorySnapshotParser``.
|
|
473
|
+
# ---------------------------------------------------------------------------
|
|
474
|
+
ASTParser = RepositorySnapshotParser
|