claude-code-workflow 6.3.13 → 6.3.15
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.
- package/.claude/agents/issue-plan-agent.md +57 -103
- package/.claude/agents/issue-queue-agent.md +69 -120
- package/.claude/commands/issue/new.md +217 -473
- package/.claude/commands/issue/plan.md +76 -154
- package/.claude/commands/issue/queue.md +208 -259
- package/.claude/skills/issue-manage/SKILL.md +63 -22
- package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +3 -3
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +3 -3
- package/.claude/workflows/cli-templates/schemas/queue-schema.json +0 -5
- package/.codex/prompts/issue-plan.md +16 -19
- package/.codex/prompts/issue-queue.md +0 -1
- package/README.md +1 -0
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +3 -1
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +45 -3
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +3 -1
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +383 -30
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/issue-routes.js +77 -16
- package/ccw/dist/core/routes/issue-routes.js.map +1 -1
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +117 -4
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/litellm-executor.d.ts +4 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-executor.js +54 -1
- package/ccw/dist/tools/litellm-executor.js.map +1 -1
- package/ccw/dist/tools/ui-generate-preview.d.ts +18 -0
- package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -1
- package/ccw/dist/tools/ui-generate-preview.js +26 -10
- package/ccw/dist/tools/ui-generate-preview.js.map +1 -1
- package/ccw/src/cli.ts +3 -1
- package/ccw/src/commands/cli.ts +47 -3
- package/ccw/src/commands/issue.ts +442 -34
- package/ccw/src/core/routes/issue-routes.ts +82 -16
- package/ccw/src/tools/cli-executor.ts +125 -4
- package/ccw/src/tools/litellm-executor.ts +107 -24
- package/ccw/src/tools/ui-generate-preview.js +60 -37
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +25 -2
- package/codex-lens/src/codexlens/entities.py +5 -1
- package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -243
- package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/factory.py +256 -256
- package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -335
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +30 -1
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/embedder.py +6 -9
- package/codex-lens/src/codexlens/semantic/vector_store.py +271 -200
- package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/sqlite_store.py +184 -108
- package/package.json +6 -1
- package/.claude/commands/issue/manage.md +0 -113
|
@@ -1,256 +1,256 @@
|
|
|
1
|
-
"""Parser factory for CodexLens.
|
|
2
|
-
|
|
3
|
-
Python and JavaScript/TypeScript parsing use Tree-Sitter grammars when
|
|
4
|
-
available. Regex fallbacks are retained to preserve the existing parser
|
|
5
|
-
interface and behavior in minimal environments.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import re
|
|
11
|
-
from dataclasses import dataclass
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Dict, List, Optional, Protocol
|
|
14
|
-
|
|
15
|
-
from codexlens.config import Config
|
|
16
|
-
from codexlens.entities import IndexedFile, Symbol
|
|
17
|
-
from codexlens.parsers.treesitter_parser import TreeSitterSymbolParser
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Parser(Protocol):
|
|
21
|
-
def parse(self, text: str, path: Path) -> IndexedFile: ...
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@dataclass
|
|
25
|
-
class SimpleRegexParser:
|
|
26
|
-
language_id: str
|
|
27
|
-
|
|
28
|
-
def parse(self, text: str, path: Path) -> IndexedFile:
|
|
29
|
-
# Try tree-sitter first for supported languages
|
|
30
|
-
if self.language_id in {"python", "javascript", "typescript"}:
|
|
31
|
-
ts_parser = TreeSitterSymbolParser(self.language_id, path)
|
|
32
|
-
if ts_parser.is_available():
|
|
33
|
-
symbols = ts_parser.parse_symbols(text)
|
|
34
|
-
if symbols is not None:
|
|
35
|
-
return IndexedFile(
|
|
36
|
-
path=str(path.resolve()),
|
|
37
|
-
language=self.language_id,
|
|
38
|
-
symbols=symbols,
|
|
39
|
-
chunks=[],
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# Fallback to regex parsing
|
|
43
|
-
if self.language_id == "python":
|
|
44
|
-
symbols = _parse_python_symbols_regex(text)
|
|
45
|
-
elif self.language_id in {"javascript", "typescript"}:
|
|
46
|
-
symbols = _parse_js_ts_symbols_regex(text)
|
|
47
|
-
elif self.language_id == "java":
|
|
48
|
-
symbols = _parse_java_symbols(text)
|
|
49
|
-
elif self.language_id == "go":
|
|
50
|
-
symbols = _parse_go_symbols(text)
|
|
51
|
-
elif self.language_id == "markdown":
|
|
52
|
-
symbols = _parse_markdown_symbols(text)
|
|
53
|
-
elif self.language_id == "text":
|
|
54
|
-
symbols = _parse_text_symbols(text)
|
|
55
|
-
else:
|
|
56
|
-
symbols = _parse_generic_symbols(text)
|
|
57
|
-
|
|
58
|
-
return IndexedFile(
|
|
59
|
-
path=str(path.resolve()),
|
|
60
|
-
language=self.language_id,
|
|
61
|
-
symbols=symbols,
|
|
62
|
-
chunks=[],
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class ParserFactory:
|
|
67
|
-
def __init__(self, config: Config) -> None:
|
|
68
|
-
self.config = config
|
|
69
|
-
self._parsers: Dict[str, Parser] = {}
|
|
70
|
-
|
|
71
|
-
def get_parser(self, language_id: str) -> Parser:
|
|
72
|
-
if language_id not in self._parsers:
|
|
73
|
-
self._parsers[language_id] = SimpleRegexParser(language_id)
|
|
74
|
-
return self._parsers[language_id]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Regex-based fallback parsers
|
|
78
|
-
_PY_CLASS_RE = re.compile(r"^\s*class\s+([A-Za-z_]\w*)\b")
|
|
79
|
-
_PY_DEF_RE = re.compile(r"^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _parse_python_symbols(text: str) -> List[Symbol]:
|
|
85
|
-
"""Parse Python symbols, using tree-sitter if available, regex fallback."""
|
|
86
|
-
ts_parser = TreeSitterSymbolParser("python")
|
|
87
|
-
if ts_parser.is_available():
|
|
88
|
-
symbols = ts_parser.parse_symbols(text)
|
|
89
|
-
if symbols is not None:
|
|
90
|
-
return symbols
|
|
91
|
-
return _parse_python_symbols_regex(text)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _parse_js_ts_symbols(
|
|
95
|
-
text: str,
|
|
96
|
-
language_id: str = "javascript",
|
|
97
|
-
path: Optional[Path] = None,
|
|
98
|
-
) -> List[Symbol]:
|
|
99
|
-
"""Parse JS/TS symbols, using tree-sitter if available, regex fallback."""
|
|
100
|
-
ts_parser = TreeSitterSymbolParser(language_id, path)
|
|
101
|
-
if ts_parser.is_available():
|
|
102
|
-
symbols = ts_parser.parse_symbols(text)
|
|
103
|
-
if symbols is not None:
|
|
104
|
-
return symbols
|
|
105
|
-
return _parse_js_ts_symbols_regex(text)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _parse_python_symbols_regex(text: str) -> List[Symbol]:
|
|
109
|
-
symbols: List[Symbol] = []
|
|
110
|
-
current_class_indent: Optional[int] = None
|
|
111
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
112
|
-
class_match = _PY_CLASS_RE.match(line)
|
|
113
|
-
if class_match:
|
|
114
|
-
current_class_indent = len(line) - len(line.lstrip(" "))
|
|
115
|
-
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
116
|
-
continue
|
|
117
|
-
def_match = _PY_DEF_RE.match(line)
|
|
118
|
-
if def_match:
|
|
119
|
-
indent = len(line) - len(line.lstrip(" "))
|
|
120
|
-
kind = "method" if current_class_indent is not None and indent > current_class_indent else "function"
|
|
121
|
-
symbols.append(Symbol(name=def_match.group(1), kind=kind, range=(i, i)))
|
|
122
|
-
continue
|
|
123
|
-
if current_class_indent is not None:
|
|
124
|
-
indent = len(line) - len(line.lstrip(" "))
|
|
125
|
-
if line.strip() and indent <= current_class_indent:
|
|
126
|
-
current_class_indent = None
|
|
127
|
-
return symbols
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
_JS_FUNC_RE = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(")
|
|
131
|
-
_JS_CLASS_RE = re.compile(r"^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b")
|
|
132
|
-
_JS_ARROW_RE = re.compile(
|
|
133
|
-
r"^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?[^)]*\)?\s*=>"
|
|
134
|
-
)
|
|
135
|
-
_JS_METHOD_RE = re.compile(r"^\s+(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _parse_js_ts_symbols_regex(text: str) -> List[Symbol]:
|
|
139
|
-
symbols: List[Symbol] = []
|
|
140
|
-
in_class = False
|
|
141
|
-
class_brace_depth = 0
|
|
142
|
-
brace_depth = 0
|
|
143
|
-
|
|
144
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
145
|
-
brace_depth += line.count("{") - line.count("}")
|
|
146
|
-
|
|
147
|
-
class_match = _JS_CLASS_RE.match(line)
|
|
148
|
-
if class_match:
|
|
149
|
-
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
150
|
-
in_class = True
|
|
151
|
-
class_brace_depth = brace_depth
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
if in_class and brace_depth < class_brace_depth:
|
|
155
|
-
in_class = False
|
|
156
|
-
|
|
157
|
-
func_match = _JS_FUNC_RE.match(line)
|
|
158
|
-
if func_match:
|
|
159
|
-
symbols.append(Symbol(name=func_match.group(1), kind="function", range=(i, i)))
|
|
160
|
-
continue
|
|
161
|
-
|
|
162
|
-
arrow_match = _JS_ARROW_RE.match(line)
|
|
163
|
-
if arrow_match:
|
|
164
|
-
symbols.append(Symbol(name=arrow_match.group(1), kind="function", range=(i, i)))
|
|
165
|
-
continue
|
|
166
|
-
|
|
167
|
-
if in_class:
|
|
168
|
-
method_match = _JS_METHOD_RE.match(line)
|
|
169
|
-
if method_match:
|
|
170
|
-
name = method_match.group(1)
|
|
171
|
-
if name != "constructor":
|
|
172
|
-
symbols.append(Symbol(name=name, kind="method", range=(i, i)))
|
|
173
|
-
|
|
174
|
-
return symbols
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
_JAVA_CLASS_RE = re.compile(r"^\s*(?:public\s+)?class\s+([A-Za-z_]\w*)\b")
|
|
178
|
-
_JAVA_METHOD_RE = re.compile(
|
|
179
|
-
r"^\s*(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+([A-Za-z_]\w*)\s*\("
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def _parse_java_symbols(text: str) -> List[Symbol]:
|
|
184
|
-
symbols: List[Symbol] = []
|
|
185
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
186
|
-
class_match = _JAVA_CLASS_RE.match(line)
|
|
187
|
-
if class_match:
|
|
188
|
-
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
189
|
-
continue
|
|
190
|
-
method_match = _JAVA_METHOD_RE.match(line)
|
|
191
|
-
if method_match:
|
|
192
|
-
symbols.append(Symbol(name=method_match.group(1), kind="method", range=(i, i)))
|
|
193
|
-
return symbols
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
_GO_FUNC_RE = re.compile(r"^\s*func\s+(?:\([^)]+\)\s+)?([A-Za-z_]\w*)\s*\(")
|
|
197
|
-
_GO_TYPE_RE = re.compile(r"^\s*type\s+([A-Za-z_]\w*)\s+(?:struct|interface)\b")
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def _parse_go_symbols(text: str) -> List[Symbol]:
|
|
201
|
-
symbols: List[Symbol] = []
|
|
202
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
203
|
-
type_match = _GO_TYPE_RE.match(line)
|
|
204
|
-
if type_match:
|
|
205
|
-
symbols.append(Symbol(name=type_match.group(1), kind="class", range=(i, i)))
|
|
206
|
-
continue
|
|
207
|
-
func_match = _GO_FUNC_RE.match(line)
|
|
208
|
-
if func_match:
|
|
209
|
-
symbols.append(Symbol(name=func_match.group(1), kind="function", range=(i, i)))
|
|
210
|
-
return symbols
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
_GENERIC_DEF_RE = re.compile(r"^\s*(?:def|function|func)\s+([A-Za-z_]\w*)\b")
|
|
214
|
-
_GENERIC_CLASS_RE = re.compile(r"^\s*(?:class|struct|interface)\s+([A-Za-z_]\w*)\b")
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def _parse_generic_symbols(text: str) -> List[Symbol]:
|
|
218
|
-
symbols: List[Symbol] = []
|
|
219
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
220
|
-
class_match = _GENERIC_CLASS_RE.match(line)
|
|
221
|
-
if class_match:
|
|
222
|
-
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
223
|
-
continue
|
|
224
|
-
def_match = _GENERIC_DEF_RE.match(line)
|
|
225
|
-
if def_match:
|
|
226
|
-
symbols.append(Symbol(name=def_match.group(1), kind="function", range=(i, i)))
|
|
227
|
-
return symbols
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Markdown heading regex: # Heading, ## Heading, etc.
|
|
231
|
-
_MD_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$")
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def _parse_markdown_symbols(text: str) -> List[Symbol]:
|
|
235
|
-
"""Parse Markdown headings as symbols.
|
|
236
|
-
|
|
237
|
-
Extracts # headings as 'section' symbols with heading level as kind suffix.
|
|
238
|
-
"""
|
|
239
|
-
symbols: List[Symbol] = []
|
|
240
|
-
for i, line in enumerate(text.splitlines(), start=1):
|
|
241
|
-
heading_match = _MD_HEADING_RE.match(line)
|
|
242
|
-
if heading_match:
|
|
243
|
-
level = len(heading_match.group(1))
|
|
244
|
-
title = heading_match.group(2).strip()
|
|
245
|
-
# Use 'section' kind with level indicator
|
|
246
|
-
kind = f"h{level}"
|
|
247
|
-
symbols.append(Symbol(name=title, kind=kind, range=(i, i)))
|
|
248
|
-
return symbols
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def _parse_text_symbols(text: str) -> List[Symbol]:
|
|
252
|
-
"""Parse plain text files - no symbols, just index content."""
|
|
253
|
-
# Text files don't have structured symbols, return empty list
|
|
254
|
-
# The file content will still be indexed for FTS search
|
|
255
|
-
return []
|
|
256
|
-
|
|
1
|
+
"""Parser factory for CodexLens.
|
|
2
|
+
|
|
3
|
+
Python and JavaScript/TypeScript parsing use Tree-Sitter grammars when
|
|
4
|
+
available. Regex fallbacks are retained to preserve the existing parser
|
|
5
|
+
interface and behavior in minimal environments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional, Protocol
|
|
14
|
+
|
|
15
|
+
from codexlens.config import Config
|
|
16
|
+
from codexlens.entities import IndexedFile, Symbol
|
|
17
|
+
from codexlens.parsers.treesitter_parser import TreeSitterSymbolParser
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Parser(Protocol):
|
|
21
|
+
def parse(self, text: str, path: Path) -> IndexedFile: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class SimpleRegexParser:
|
|
26
|
+
language_id: str
|
|
27
|
+
|
|
28
|
+
def parse(self, text: str, path: Path) -> IndexedFile:
|
|
29
|
+
# Try tree-sitter first for supported languages
|
|
30
|
+
if self.language_id in {"python", "javascript", "typescript"}:
|
|
31
|
+
ts_parser = TreeSitterSymbolParser(self.language_id, path)
|
|
32
|
+
if ts_parser.is_available():
|
|
33
|
+
symbols = ts_parser.parse_symbols(text)
|
|
34
|
+
if symbols is not None:
|
|
35
|
+
return IndexedFile(
|
|
36
|
+
path=str(path.resolve()),
|
|
37
|
+
language=self.language_id,
|
|
38
|
+
symbols=symbols,
|
|
39
|
+
chunks=[],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Fallback to regex parsing
|
|
43
|
+
if self.language_id == "python":
|
|
44
|
+
symbols = _parse_python_symbols_regex(text)
|
|
45
|
+
elif self.language_id in {"javascript", "typescript"}:
|
|
46
|
+
symbols = _parse_js_ts_symbols_regex(text)
|
|
47
|
+
elif self.language_id == "java":
|
|
48
|
+
symbols = _parse_java_symbols(text)
|
|
49
|
+
elif self.language_id == "go":
|
|
50
|
+
symbols = _parse_go_symbols(text)
|
|
51
|
+
elif self.language_id == "markdown":
|
|
52
|
+
symbols = _parse_markdown_symbols(text)
|
|
53
|
+
elif self.language_id == "text":
|
|
54
|
+
symbols = _parse_text_symbols(text)
|
|
55
|
+
else:
|
|
56
|
+
symbols = _parse_generic_symbols(text)
|
|
57
|
+
|
|
58
|
+
return IndexedFile(
|
|
59
|
+
path=str(path.resolve()),
|
|
60
|
+
language=self.language_id,
|
|
61
|
+
symbols=symbols,
|
|
62
|
+
chunks=[],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ParserFactory:
|
|
67
|
+
def __init__(self, config: Config) -> None:
|
|
68
|
+
self.config = config
|
|
69
|
+
self._parsers: Dict[str, Parser] = {}
|
|
70
|
+
|
|
71
|
+
def get_parser(self, language_id: str) -> Parser:
|
|
72
|
+
if language_id not in self._parsers:
|
|
73
|
+
self._parsers[language_id] = SimpleRegexParser(language_id)
|
|
74
|
+
return self._parsers[language_id]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Regex-based fallback parsers
|
|
78
|
+
_PY_CLASS_RE = re.compile(r"^\s*class\s+([A-Za-z_]\w*)\b")
|
|
79
|
+
_PY_DEF_RE = re.compile(r"^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _parse_python_symbols(text: str) -> List[Symbol]:
|
|
85
|
+
"""Parse Python symbols, using tree-sitter if available, regex fallback."""
|
|
86
|
+
ts_parser = TreeSitterSymbolParser("python")
|
|
87
|
+
if ts_parser.is_available():
|
|
88
|
+
symbols = ts_parser.parse_symbols(text)
|
|
89
|
+
if symbols is not None:
|
|
90
|
+
return symbols
|
|
91
|
+
return _parse_python_symbols_regex(text)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _parse_js_ts_symbols(
|
|
95
|
+
text: str,
|
|
96
|
+
language_id: str = "javascript",
|
|
97
|
+
path: Optional[Path] = None,
|
|
98
|
+
) -> List[Symbol]:
|
|
99
|
+
"""Parse JS/TS symbols, using tree-sitter if available, regex fallback."""
|
|
100
|
+
ts_parser = TreeSitterSymbolParser(language_id, path)
|
|
101
|
+
if ts_parser.is_available():
|
|
102
|
+
symbols = ts_parser.parse_symbols(text)
|
|
103
|
+
if symbols is not None:
|
|
104
|
+
return symbols
|
|
105
|
+
return _parse_js_ts_symbols_regex(text)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _parse_python_symbols_regex(text: str) -> List[Symbol]:
|
|
109
|
+
symbols: List[Symbol] = []
|
|
110
|
+
current_class_indent: Optional[int] = None
|
|
111
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
112
|
+
class_match = _PY_CLASS_RE.match(line)
|
|
113
|
+
if class_match:
|
|
114
|
+
current_class_indent = len(line) - len(line.lstrip(" "))
|
|
115
|
+
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
116
|
+
continue
|
|
117
|
+
def_match = _PY_DEF_RE.match(line)
|
|
118
|
+
if def_match:
|
|
119
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
120
|
+
kind = "method" if current_class_indent is not None and indent > current_class_indent else "function"
|
|
121
|
+
symbols.append(Symbol(name=def_match.group(1), kind=kind, range=(i, i)))
|
|
122
|
+
continue
|
|
123
|
+
if current_class_indent is not None:
|
|
124
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
125
|
+
if line.strip() and indent <= current_class_indent:
|
|
126
|
+
current_class_indent = None
|
|
127
|
+
return symbols
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
_JS_FUNC_RE = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(")
|
|
131
|
+
_JS_CLASS_RE = re.compile(r"^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b")
|
|
132
|
+
_JS_ARROW_RE = re.compile(
|
|
133
|
+
r"^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?[^)]*\)?\s*=>"
|
|
134
|
+
)
|
|
135
|
+
_JS_METHOD_RE = re.compile(r"^\s+(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _parse_js_ts_symbols_regex(text: str) -> List[Symbol]:
|
|
139
|
+
symbols: List[Symbol] = []
|
|
140
|
+
in_class = False
|
|
141
|
+
class_brace_depth = 0
|
|
142
|
+
brace_depth = 0
|
|
143
|
+
|
|
144
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
145
|
+
brace_depth += line.count("{") - line.count("}")
|
|
146
|
+
|
|
147
|
+
class_match = _JS_CLASS_RE.match(line)
|
|
148
|
+
if class_match:
|
|
149
|
+
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
150
|
+
in_class = True
|
|
151
|
+
class_brace_depth = brace_depth
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
if in_class and brace_depth < class_brace_depth:
|
|
155
|
+
in_class = False
|
|
156
|
+
|
|
157
|
+
func_match = _JS_FUNC_RE.match(line)
|
|
158
|
+
if func_match:
|
|
159
|
+
symbols.append(Symbol(name=func_match.group(1), kind="function", range=(i, i)))
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
arrow_match = _JS_ARROW_RE.match(line)
|
|
163
|
+
if arrow_match:
|
|
164
|
+
symbols.append(Symbol(name=arrow_match.group(1), kind="function", range=(i, i)))
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if in_class:
|
|
168
|
+
method_match = _JS_METHOD_RE.match(line)
|
|
169
|
+
if method_match:
|
|
170
|
+
name = method_match.group(1)
|
|
171
|
+
if name != "constructor":
|
|
172
|
+
symbols.append(Symbol(name=name, kind="method", range=(i, i)))
|
|
173
|
+
|
|
174
|
+
return symbols
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
_JAVA_CLASS_RE = re.compile(r"^\s*(?:public\s+)?class\s+([A-Za-z_]\w*)\b")
|
|
178
|
+
_JAVA_METHOD_RE = re.compile(
|
|
179
|
+
r"^\s*(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+([A-Za-z_]\w*)\s*\("
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _parse_java_symbols(text: str) -> List[Symbol]:
|
|
184
|
+
symbols: List[Symbol] = []
|
|
185
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
186
|
+
class_match = _JAVA_CLASS_RE.match(line)
|
|
187
|
+
if class_match:
|
|
188
|
+
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
189
|
+
continue
|
|
190
|
+
method_match = _JAVA_METHOD_RE.match(line)
|
|
191
|
+
if method_match:
|
|
192
|
+
symbols.append(Symbol(name=method_match.group(1), kind="method", range=(i, i)))
|
|
193
|
+
return symbols
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
_GO_FUNC_RE = re.compile(r"^\s*func\s+(?:\([^)]+\)\s+)?([A-Za-z_]\w*)\s*\(")
|
|
197
|
+
_GO_TYPE_RE = re.compile(r"^\s*type\s+([A-Za-z_]\w*)\s+(?:struct|interface)\b")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _parse_go_symbols(text: str) -> List[Symbol]:
|
|
201
|
+
symbols: List[Symbol] = []
|
|
202
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
203
|
+
type_match = _GO_TYPE_RE.match(line)
|
|
204
|
+
if type_match:
|
|
205
|
+
symbols.append(Symbol(name=type_match.group(1), kind="class", range=(i, i)))
|
|
206
|
+
continue
|
|
207
|
+
func_match = _GO_FUNC_RE.match(line)
|
|
208
|
+
if func_match:
|
|
209
|
+
symbols.append(Symbol(name=func_match.group(1), kind="function", range=(i, i)))
|
|
210
|
+
return symbols
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
_GENERIC_DEF_RE = re.compile(r"^\s*(?:def|function|func)\s+([A-Za-z_]\w*)\b")
|
|
214
|
+
_GENERIC_CLASS_RE = re.compile(r"^\s*(?:class|struct|interface)\s+([A-Za-z_]\w*)\b")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _parse_generic_symbols(text: str) -> List[Symbol]:
|
|
218
|
+
symbols: List[Symbol] = []
|
|
219
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
220
|
+
class_match = _GENERIC_CLASS_RE.match(line)
|
|
221
|
+
if class_match:
|
|
222
|
+
symbols.append(Symbol(name=class_match.group(1), kind="class", range=(i, i)))
|
|
223
|
+
continue
|
|
224
|
+
def_match = _GENERIC_DEF_RE.match(line)
|
|
225
|
+
if def_match:
|
|
226
|
+
symbols.append(Symbol(name=def_match.group(1), kind="function", range=(i, i)))
|
|
227
|
+
return symbols
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Markdown heading regex: # Heading, ## Heading, etc.
|
|
231
|
+
_MD_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _parse_markdown_symbols(text: str) -> List[Symbol]:
|
|
235
|
+
"""Parse Markdown headings as symbols.
|
|
236
|
+
|
|
237
|
+
Extracts # headings as 'section' symbols with heading level as kind suffix.
|
|
238
|
+
"""
|
|
239
|
+
symbols: List[Symbol] = []
|
|
240
|
+
for i, line in enumerate(text.splitlines(), start=1):
|
|
241
|
+
heading_match = _MD_HEADING_RE.match(line)
|
|
242
|
+
if heading_match:
|
|
243
|
+
level = len(heading_match.group(1))
|
|
244
|
+
title = heading_match.group(2).strip()
|
|
245
|
+
# Use 'section' kind with level indicator
|
|
246
|
+
kind = f"h{level}"
|
|
247
|
+
symbols.append(Symbol(name=title, kind=kind, range=(i, i)))
|
|
248
|
+
return symbols
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _parse_text_symbols(text: str) -> List[Symbol]:
|
|
252
|
+
"""Parse plain text files - no symbols, just index content."""
|
|
253
|
+
# Text files don't have structured symbols, return empty list
|
|
254
|
+
# The file content will still be indexed for FTS search
|
|
255
|
+
return []
|
|
256
|
+
|