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,243 +1,243 @@
1
- """Symbol and relationship extraction from source code."""
2
- import re
3
- import sqlite3
4
- from pathlib import Path
5
- from typing import Any, Dict, List, Optional, Tuple
6
-
7
-
8
- class SymbolExtractor:
9
- """Extract symbols and relationships from source code using regex patterns."""
10
-
11
- # Pattern definitions for different languages
12
- PATTERNS = {
13
- 'python': {
14
- 'function': r'^(?:async\s+)?def\s+(\w+)\s*\(',
15
- 'class': r'^class\s+(\w+)\s*[:\(]',
16
- 'import': r'^(?:from\s+([\w.]+)\s+)?import\s+([\w.,\s]+)',
17
- 'call': r'(?<![.\w])(\w+)\s*\(',
18
- },
19
- 'typescript': {
20
- 'function': r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*[<\(]',
21
- 'class': r'(?:export\s+)?class\s+(\w+)',
22
- 'import': r"import\s+.*\s+from\s+['\"]([^'\"]+)['\"]",
23
- 'call': r'(?<![.\w])(\w+)\s*[<\(]',
24
- },
25
- 'javascript': {
26
- 'function': r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(',
27
- 'class': r'(?:export\s+)?class\s+(\w+)',
28
- 'import': r"(?:import|require)\s*\(?['\"]([^'\"]+)['\"]",
29
- 'call': r'(?<![.\w])(\w+)\s*\(',
30
- }
31
- }
32
-
33
- LANGUAGE_MAP = {
34
- '.py': 'python',
35
- '.ts': 'typescript',
36
- '.tsx': 'typescript',
37
- '.js': 'javascript',
38
- '.jsx': 'javascript',
39
- }
40
-
41
- def __init__(self, db_path: Path):
42
- self.db_path = db_path
43
- self.db_conn: Optional[sqlite3.Connection] = None
44
-
45
- def connect(self) -> None:
46
- """Connect to database and ensure schema exists."""
47
- self.db_conn = sqlite3.connect(str(self.db_path))
48
- self._ensure_tables()
49
-
50
- def __enter__(self) -> "SymbolExtractor":
51
- """Context manager entry: connect to database."""
52
- self.connect()
53
- return self
54
-
55
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
56
- """Context manager exit: close database connection."""
57
- self.close()
58
-
59
- def _ensure_tables(self) -> None:
60
- """Create symbols and relationships tables if they don't exist."""
61
- if not self.db_conn:
62
- return
63
- cursor = self.db_conn.cursor()
64
-
65
- # Create symbols table with qualified_name
66
- cursor.execute('''
67
- CREATE TABLE IF NOT EXISTS symbols (
68
- id INTEGER PRIMARY KEY AUTOINCREMENT,
69
- qualified_name TEXT NOT NULL,
70
- name TEXT NOT NULL,
71
- kind TEXT NOT NULL,
72
- file_path TEXT NOT NULL,
73
- start_line INTEGER NOT NULL,
74
- end_line INTEGER NOT NULL,
75
- UNIQUE(file_path, name, start_line)
76
- )
77
- ''')
78
-
79
- # Create relationships table with target_symbol_fqn
80
- cursor.execute('''
81
- CREATE TABLE IF NOT EXISTS symbol_relationships (
82
- id INTEGER PRIMARY KEY AUTOINCREMENT,
83
- source_symbol_id INTEGER NOT NULL,
84
- target_symbol_fqn TEXT NOT NULL,
85
- relationship_type TEXT NOT NULL,
86
- file_path TEXT NOT NULL,
87
- line INTEGER,
88
- FOREIGN KEY (source_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
89
- )
90
- ''')
91
-
92
- # Create performance indexes
93
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)')
94
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path)')
95
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_source ON symbol_relationships(source_symbol_id)')
96
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_target ON symbol_relationships(target_symbol_fqn)')
97
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_type ON symbol_relationships(relationship_type)')
98
-
99
- self.db_conn.commit()
100
-
101
- def extract_from_file(self, file_path: Path, content: str) -> Tuple[List[Dict], List[Dict]]:
102
- """Extract symbols and relationships from file content.
103
-
104
- Args:
105
- file_path: Path to the source file
106
- content: File content as string
107
-
108
- Returns:
109
- Tuple of (symbols, relationships) where:
110
- - symbols: List of symbol dicts with qualified_name, name, kind, file_path, start_line, end_line
111
- - relationships: List of relationship dicts with source_scope, target, type, file_path, line
112
- """
113
- ext = file_path.suffix.lower()
114
- lang = self.LANGUAGE_MAP.get(ext)
115
-
116
- if not lang or lang not in self.PATTERNS:
117
- return [], []
118
-
119
- patterns = self.PATTERNS[lang]
120
- symbols = []
121
- relationships = []
122
- lines = content.split('\n')
123
-
124
- current_scope = None
125
-
126
- for line_num, line in enumerate(lines, 1):
127
- # Extract function/class definitions
128
- for kind in ['function', 'class']:
129
- if kind in patterns:
130
- match = re.search(patterns[kind], line)
131
- if match:
132
- name = match.group(1)
133
- qualified_name = f"{file_path.stem}.{name}"
134
- symbols.append({
135
- 'qualified_name': qualified_name,
136
- 'name': name,
137
- 'kind': kind,
138
- 'file_path': str(file_path),
139
- 'start_line': line_num,
140
- 'end_line': line_num, # Simplified - would need proper parsing for actual end
141
- })
142
- current_scope = name
143
-
144
- # Extract imports
145
- if 'import' in patterns:
146
- match = re.search(patterns['import'], line)
147
- if match:
148
- import_target = match.group(1) or match.group(2) if match.lastindex >= 2 else match.group(1)
149
- if import_target and current_scope:
150
- relationships.append({
151
- 'source_scope': current_scope,
152
- 'target': import_target.strip(),
153
- 'type': 'imports',
154
- 'file_path': str(file_path),
155
- 'line': line_num,
156
- })
157
-
158
- # Extract function calls (simplified)
159
- if 'call' in patterns and current_scope:
160
- for match in re.finditer(patterns['call'], line):
161
- call_name = match.group(1)
162
- # Skip common keywords and the current function
163
- if call_name not in ['if', 'for', 'while', 'return', 'print', 'len', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple', current_scope]:
164
- relationships.append({
165
- 'source_scope': current_scope,
166
- 'target': call_name,
167
- 'type': 'calls',
168
- 'file_path': str(file_path),
169
- 'line': line_num,
170
- })
171
-
172
- return symbols, relationships
173
-
174
- def save_symbols(self, symbols: List[Dict]) -> Dict[str, int]:
175
- """Save symbols to database and return name->id mapping.
176
-
177
- Args:
178
- symbols: List of symbol dicts with qualified_name, name, kind, file_path, start_line, end_line
179
-
180
- Returns:
181
- Dictionary mapping symbol name to database id
182
- """
183
- if not self.db_conn or not symbols:
184
- return {}
185
-
186
- cursor = self.db_conn.cursor()
187
- name_to_id = {}
188
-
189
- for sym in symbols:
190
- try:
191
- cursor.execute('''
192
- INSERT OR IGNORE INTO symbols
193
- (qualified_name, name, kind, file_path, start_line, end_line)
194
- VALUES (?, ?, ?, ?, ?, ?)
195
- ''', (sym['qualified_name'], sym['name'], sym['kind'],
196
- sym['file_path'], sym['start_line'], sym['end_line']))
197
-
198
- # Get the id
199
- cursor.execute('''
200
- SELECT id FROM symbols
201
- WHERE file_path = ? AND name = ? AND start_line = ?
202
- ''', (sym['file_path'], sym['name'], sym['start_line']))
203
-
204
- row = cursor.fetchone()
205
- if row:
206
- name_to_id[sym['name']] = row[0]
207
- except sqlite3.Error:
208
- continue
209
-
210
- self.db_conn.commit()
211
- return name_to_id
212
-
213
- def save_relationships(self, relationships: List[Dict], name_to_id: Dict[str, int]) -> None:
214
- """Save relationships to database.
215
-
216
- Args:
217
- relationships: List of relationship dicts with source_scope, target, type, file_path, line
218
- name_to_id: Dictionary mapping symbol names to database ids
219
- """
220
- if not self.db_conn or not relationships:
221
- return
222
-
223
- cursor = self.db_conn.cursor()
224
-
225
- for rel in relationships:
226
- source_id = name_to_id.get(rel['source_scope'])
227
- if source_id:
228
- try:
229
- cursor.execute('''
230
- INSERT INTO symbol_relationships
231
- (source_symbol_id, target_symbol_fqn, relationship_type, file_path, line)
232
- VALUES (?, ?, ?, ?, ?)
233
- ''', (source_id, rel['target'], rel['type'], rel['file_path'], rel['line']))
234
- except sqlite3.Error:
235
- continue
236
-
237
- self.db_conn.commit()
238
-
239
- def close(self) -> None:
240
- """Close database connection."""
241
- if self.db_conn:
242
- self.db_conn.close()
243
- self.db_conn = None
1
+ """Symbol and relationship extraction from source code."""
2
+ import re
3
+ import sqlite3
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional, Tuple
6
+
7
+
8
+ class SymbolExtractor:
9
+ """Extract symbols and relationships from source code using regex patterns."""
10
+
11
+ # Pattern definitions for different languages
12
+ PATTERNS = {
13
+ 'python': {
14
+ 'function': r'^(?:async\s+)?def\s+(\w+)\s*\(',
15
+ 'class': r'^class\s+(\w+)\s*[:\(]',
16
+ 'import': r'^(?:from\s+([\w.]+)\s+)?import\s+([\w.,\s]+)',
17
+ 'call': r'(?<![.\w])(\w+)\s*\(',
18
+ },
19
+ 'typescript': {
20
+ 'function': r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*[<\(]',
21
+ 'class': r'(?:export\s+)?class\s+(\w+)',
22
+ 'import': r"import\s+.*\s+from\s+['\"]([^'\"]+)['\"]",
23
+ 'call': r'(?<![.\w])(\w+)\s*[<\(]',
24
+ },
25
+ 'javascript': {
26
+ 'function': r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(',
27
+ 'class': r'(?:export\s+)?class\s+(\w+)',
28
+ 'import': r"(?:import|require)\s*\(?['\"]([^'\"]+)['\"]",
29
+ 'call': r'(?<![.\w])(\w+)\s*\(',
30
+ }
31
+ }
32
+
33
+ LANGUAGE_MAP = {
34
+ '.py': 'python',
35
+ '.ts': 'typescript',
36
+ '.tsx': 'typescript',
37
+ '.js': 'javascript',
38
+ '.jsx': 'javascript',
39
+ }
40
+
41
+ def __init__(self, db_path: Path):
42
+ self.db_path = db_path
43
+ self.db_conn: Optional[sqlite3.Connection] = None
44
+
45
+ def connect(self) -> None:
46
+ """Connect to database and ensure schema exists."""
47
+ self.db_conn = sqlite3.connect(str(self.db_path))
48
+ self._ensure_tables()
49
+
50
+ def __enter__(self) -> "SymbolExtractor":
51
+ """Context manager entry: connect to database."""
52
+ self.connect()
53
+ return self
54
+
55
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
56
+ """Context manager exit: close database connection."""
57
+ self.close()
58
+
59
+ def _ensure_tables(self) -> None:
60
+ """Create symbols and relationships tables if they don't exist."""
61
+ if not self.db_conn:
62
+ return
63
+ cursor = self.db_conn.cursor()
64
+
65
+ # Create symbols table with qualified_name
66
+ cursor.execute('''
67
+ CREATE TABLE IF NOT EXISTS symbols (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ qualified_name TEXT NOT NULL,
70
+ name TEXT NOT NULL,
71
+ kind TEXT NOT NULL,
72
+ file_path TEXT NOT NULL,
73
+ start_line INTEGER NOT NULL,
74
+ end_line INTEGER NOT NULL,
75
+ UNIQUE(file_path, name, start_line)
76
+ )
77
+ ''')
78
+
79
+ # Create relationships table with target_symbol_fqn
80
+ cursor.execute('''
81
+ CREATE TABLE IF NOT EXISTS symbol_relationships (
82
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
83
+ source_symbol_id INTEGER NOT NULL,
84
+ target_symbol_fqn TEXT NOT NULL,
85
+ relationship_type TEXT NOT NULL,
86
+ file_path TEXT NOT NULL,
87
+ line INTEGER,
88
+ FOREIGN KEY (source_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
89
+ )
90
+ ''')
91
+
92
+ # Create performance indexes
93
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)')
94
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path)')
95
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_source ON symbol_relationships(source_symbol_id)')
96
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_target ON symbol_relationships(target_symbol_fqn)')
97
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_rel_type ON symbol_relationships(relationship_type)')
98
+
99
+ self.db_conn.commit()
100
+
101
+ def extract_from_file(self, file_path: Path, content: str) -> Tuple[List[Dict], List[Dict]]:
102
+ """Extract symbols and relationships from file content.
103
+
104
+ Args:
105
+ file_path: Path to the source file
106
+ content: File content as string
107
+
108
+ Returns:
109
+ Tuple of (symbols, relationships) where:
110
+ - symbols: List of symbol dicts with qualified_name, name, kind, file_path, start_line, end_line
111
+ - relationships: List of relationship dicts with source_scope, target, type, file_path, line
112
+ """
113
+ ext = file_path.suffix.lower()
114
+ lang = self.LANGUAGE_MAP.get(ext)
115
+
116
+ if not lang or lang not in self.PATTERNS:
117
+ return [], []
118
+
119
+ patterns = self.PATTERNS[lang]
120
+ symbols = []
121
+ relationships = []
122
+ lines = content.split('\n')
123
+
124
+ current_scope = None
125
+
126
+ for line_num, line in enumerate(lines, 1):
127
+ # Extract function/class definitions
128
+ for kind in ['function', 'class']:
129
+ if kind in patterns:
130
+ match = re.search(patterns[kind], line)
131
+ if match:
132
+ name = match.group(1)
133
+ qualified_name = f"{file_path.stem}.{name}"
134
+ symbols.append({
135
+ 'qualified_name': qualified_name,
136
+ 'name': name,
137
+ 'kind': kind,
138
+ 'file_path': str(file_path),
139
+ 'start_line': line_num,
140
+ 'end_line': line_num, # Simplified - would need proper parsing for actual end
141
+ })
142
+ current_scope = name
143
+
144
+ # Extract imports
145
+ if 'import' in patterns:
146
+ match = re.search(patterns['import'], line)
147
+ if match:
148
+ import_target = match.group(1) or match.group(2) if match.lastindex >= 2 else match.group(1)
149
+ if import_target and current_scope:
150
+ relationships.append({
151
+ 'source_scope': current_scope,
152
+ 'target': import_target.strip(),
153
+ 'type': 'imports',
154
+ 'file_path': str(file_path),
155
+ 'line': line_num,
156
+ })
157
+
158
+ # Extract function calls (simplified)
159
+ if 'call' in patterns and current_scope:
160
+ for match in re.finditer(patterns['call'], line):
161
+ call_name = match.group(1)
162
+ # Skip common keywords and the current function
163
+ if call_name not in ['if', 'for', 'while', 'return', 'print', 'len', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple', current_scope]:
164
+ relationships.append({
165
+ 'source_scope': current_scope,
166
+ 'target': call_name,
167
+ 'type': 'calls',
168
+ 'file_path': str(file_path),
169
+ 'line': line_num,
170
+ })
171
+
172
+ return symbols, relationships
173
+
174
+ def save_symbols(self, symbols: List[Dict]) -> Dict[str, int]:
175
+ """Save symbols to database and return name->id mapping.
176
+
177
+ Args:
178
+ symbols: List of symbol dicts with qualified_name, name, kind, file_path, start_line, end_line
179
+
180
+ Returns:
181
+ Dictionary mapping symbol name to database id
182
+ """
183
+ if not self.db_conn or not symbols:
184
+ return {}
185
+
186
+ cursor = self.db_conn.cursor()
187
+ name_to_id = {}
188
+
189
+ for sym in symbols:
190
+ try:
191
+ cursor.execute('''
192
+ INSERT OR IGNORE INTO symbols
193
+ (qualified_name, name, kind, file_path, start_line, end_line)
194
+ VALUES (?, ?, ?, ?, ?, ?)
195
+ ''', (sym['qualified_name'], sym['name'], sym['kind'],
196
+ sym['file_path'], sym['start_line'], sym['end_line']))
197
+
198
+ # Get the id
199
+ cursor.execute('''
200
+ SELECT id FROM symbols
201
+ WHERE file_path = ? AND name = ? AND start_line = ?
202
+ ''', (sym['file_path'], sym['name'], sym['start_line']))
203
+
204
+ row = cursor.fetchone()
205
+ if row:
206
+ name_to_id[sym['name']] = row[0]
207
+ except sqlite3.Error:
208
+ continue
209
+
210
+ self.db_conn.commit()
211
+ return name_to_id
212
+
213
+ def save_relationships(self, relationships: List[Dict], name_to_id: Dict[str, int]) -> None:
214
+ """Save relationships to database.
215
+
216
+ Args:
217
+ relationships: List of relationship dicts with source_scope, target, type, file_path, line
218
+ name_to_id: Dictionary mapping symbol names to database ids
219
+ """
220
+ if not self.db_conn or not relationships:
221
+ return
222
+
223
+ cursor = self.db_conn.cursor()
224
+
225
+ for rel in relationships:
226
+ source_id = name_to_id.get(rel['source_scope'])
227
+ if source_id:
228
+ try:
229
+ cursor.execute('''
230
+ INSERT INTO symbol_relationships
231
+ (source_symbol_id, target_symbol_fqn, relationship_type, file_path, line)
232
+ VALUES (?, ?, ?, ?, ?)
233
+ ''', (source_id, rel['target'], rel['type'], rel['file_path'], rel['line']))
234
+ except sqlite3.Error:
235
+ continue
236
+
237
+ self.db_conn.commit()
238
+
239
+ def close(self) -> None:
240
+ """Close database connection."""
241
+ if self.db_conn:
242
+ self.db_conn.close()
243
+ self.db_conn = None