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.
Files changed (69) hide show
  1. package/.claude/agents/issue-plan-agent.md +57 -103
  2. package/.claude/agents/issue-queue-agent.md +69 -120
  3. package/.claude/commands/issue/new.md +217 -473
  4. package/.claude/commands/issue/plan.md +76 -154
  5. package/.claude/commands/issue/queue.md +208 -259
  6. package/.claude/skills/issue-manage/SKILL.md +63 -22
  7. package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +3 -3
  8. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +3 -3
  9. package/.claude/workflows/cli-templates/schemas/queue-schema.json +0 -5
  10. package/.codex/prompts/issue-plan.md +16 -19
  11. package/.codex/prompts/issue-queue.md +0 -1
  12. package/README.md +1 -0
  13. package/ccw/dist/cli.d.ts.map +1 -1
  14. package/ccw/dist/cli.js +3 -1
  15. package/ccw/dist/cli.js.map +1 -1
  16. package/ccw/dist/commands/cli.d.ts.map +1 -1
  17. package/ccw/dist/commands/cli.js +45 -3
  18. package/ccw/dist/commands/cli.js.map +1 -1
  19. package/ccw/dist/commands/issue.d.ts +3 -1
  20. package/ccw/dist/commands/issue.d.ts.map +1 -1
  21. package/ccw/dist/commands/issue.js +383 -30
  22. package/ccw/dist/commands/issue.js.map +1 -1
  23. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
  24. package/ccw/dist/core/routes/issue-routes.js +77 -16
  25. package/ccw/dist/core/routes/issue-routes.js.map +1 -1
  26. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  27. package/ccw/dist/tools/cli-executor.js +117 -4
  28. package/ccw/dist/tools/cli-executor.js.map +1 -1
  29. package/ccw/dist/tools/litellm-executor.d.ts +4 -0
  30. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
  31. package/ccw/dist/tools/litellm-executor.js +54 -1
  32. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  33. package/ccw/dist/tools/ui-generate-preview.d.ts +18 -0
  34. package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -1
  35. package/ccw/dist/tools/ui-generate-preview.js +26 -10
  36. package/ccw/dist/tools/ui-generate-preview.js.map +1 -1
  37. package/ccw/src/cli.ts +3 -1
  38. package/ccw/src/commands/cli.ts +47 -3
  39. package/ccw/src/commands/issue.ts +442 -34
  40. package/ccw/src/core/routes/issue-routes.ts +82 -16
  41. package/ccw/src/tools/cli-executor.ts +125 -4
  42. package/ccw/src/tools/litellm-executor.ts +107 -24
  43. package/ccw/src/tools/ui-generate-preview.js +60 -37
  44. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  45. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  46. package/codex-lens/src/codexlens/config.py +25 -2
  47. package/codex-lens/src/codexlens/entities.py +5 -1
  48. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  49. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -243
  50. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  51. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  52. package/codex-lens/src/codexlens/parsers/factory.py +256 -256
  53. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -335
  54. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  55. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  56. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  57. package/codex-lens/src/codexlens/search/chain_search.py +30 -1
  58. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  59. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  60. package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
  61. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  62. package/codex-lens/src/codexlens/semantic/embedder.py +6 -9
  63. package/codex-lens/src/codexlens/semantic/vector_store.py +271 -200
  64. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  65. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  66. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  67. package/codex-lens/src/codexlens/storage/sqlite_store.py +184 -108
  68. package/package.json +6 -1
  69. 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
+