claude-code-workflow 6.3.16 → 6.3.18
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/CLAUDE.md +2 -2
- package/.claude/agents/action-planning-agent.md +3 -0
- package/.claude/agents/cli-execution-agent.md +2 -0
- package/.claude/agents/cli-explore-agent.md +3 -0
- package/.claude/agents/cli-lite-planning-agent.md +3 -0
- package/.claude/agents/cli-planning-agent.md +3 -0
- package/.claude/agents/code-developer.md +3 -0
- package/.claude/agents/context-search-agent.md +3 -0
- package/.claude/agents/doc-generator.md +3 -0
- package/.claude/agents/issue-plan-agent.md +3 -0
- package/.claude/agents/issue-queue-agent.md +3 -0
- package/.claude/agents/universal-executor.md +3 -0
- package/.claude/commands/issue/new.md +323 -309
- package/.codex/AGENTS.md +6 -0
- package/.codex/prompts/issue-plan.md +46 -1
- package/.codex/prompts/issue-queue.md +45 -3
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +106 -14
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +29 -4
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.js +8 -3
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
- package/ccw/dist/tools/cli-executor.d.ts +5 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +24 -0
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +2 -19
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/utils/python-utils.d.ts +36 -0
- package/ccw/dist/utils/python-utils.d.ts.map +1 -0
- package/ccw/dist/utils/python-utils.js +115 -0
- package/ccw/dist/utils/python-utils.js.map +1 -0
- package/ccw/src/commands/cli.ts +120 -14
- package/ccw/src/commands/issue.ts +31 -4
- package/ccw/src/core/routes/litellm-api-routes.ts +10 -3
- package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +8 -12
- package/ccw/src/tools/cli-executor.ts +28 -0
- package/ccw/src/tools/codex-lens.ts +2 -19
- package/ccw/src/utils/python-utils.ts +121 -0
- 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/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
- 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/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/enrichment.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/graph_expander.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/enrichment.py +150 -150
- package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
- 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/migrations/__pycache__/migration_006_enhance_relationships.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_007_add_graph_neighbors.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_008_add_merkle_hashes.cpython-313.pyc +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python detection and version compatibility utilities
|
|
3
|
+
* Shared module for consistent Python discovery across the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse Python version string to major.minor numbers
|
|
10
|
+
* @param versionStr - Version string like "Python 3.11.5"
|
|
11
|
+
* @returns Object with major and minor version numbers, or null if parsing fails
|
|
12
|
+
*/
|
|
13
|
+
export function parsePythonVersion(versionStr: string): { major: number; minor: number } | null {
|
|
14
|
+
const match = versionStr.match(/Python\s+(\d+)\.(\d+)/);
|
|
15
|
+
if (match) {
|
|
16
|
+
return { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) };
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if Python version is compatible with onnxruntime (3.9-3.12)
|
|
23
|
+
* @param major - Major version number
|
|
24
|
+
* @param minor - Minor version number
|
|
25
|
+
* @returns true if compatible
|
|
26
|
+
*/
|
|
27
|
+
export function isPythonVersionCompatible(major: number, minor: number): boolean {
|
|
28
|
+
// onnxruntime currently supports Python 3.9-3.12
|
|
29
|
+
return major === 3 && minor >= 9 && minor <= 12;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect available Python 3 executable
|
|
34
|
+
* Supports CCW_PYTHON environment variable for custom Python path
|
|
35
|
+
* On Windows, uses py launcher to find compatible versions
|
|
36
|
+
* @returns Python executable command
|
|
37
|
+
*/
|
|
38
|
+
export function getSystemPython(): string {
|
|
39
|
+
// Check for user-specified Python via environment variable
|
|
40
|
+
const customPython = process.env.CCW_PYTHON;
|
|
41
|
+
if (customPython) {
|
|
42
|
+
try {
|
|
43
|
+
const version = execSync(`"${customPython}" --version 2>&1`, { encoding: 'utf8' });
|
|
44
|
+
if (version.includes('Python 3')) {
|
|
45
|
+
const parsed = parsePythonVersion(version);
|
|
46
|
+
if (parsed && !isPythonVersionCompatible(parsed.major, parsed.minor)) {
|
|
47
|
+
console.warn(`[Python] Warning: CCW_PYTHON points to Python ${parsed.major}.${parsed.minor}, which may not be compatible with onnxruntime (requires 3.9-3.12)`);
|
|
48
|
+
}
|
|
49
|
+
return `"${customPython}"`;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
console.warn(`[Python] Warning: CCW_PYTHON="${customPython}" is not a valid Python executable, falling back to system Python`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// On Windows, try py launcher with specific versions first (3.12, 3.11, 3.10, 3.9)
|
|
57
|
+
if (process.platform === 'win32') {
|
|
58
|
+
const compatibleVersions = ['3.12', '3.11', '3.10', '3.9'];
|
|
59
|
+
for (const ver of compatibleVersions) {
|
|
60
|
+
try {
|
|
61
|
+
const version = execSync(`py -${ver} --version 2>&1`, { encoding: 'utf8' });
|
|
62
|
+
if (version.includes(`Python ${ver}`)) {
|
|
63
|
+
console.log(`[Python] Found compatible Python ${ver} via py launcher`);
|
|
64
|
+
return `py -${ver}`;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Version not installed, try next
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const commands = process.platform === 'win32' ? ['python', 'py', 'python3'] : ['python3', 'python'];
|
|
73
|
+
let fallbackCmd: string | null = null;
|
|
74
|
+
let fallbackVersion: { major: number; minor: number } | null = null;
|
|
75
|
+
|
|
76
|
+
for (const cmd of commands) {
|
|
77
|
+
try {
|
|
78
|
+
const version = execSync(`${cmd} --version 2>&1`, { encoding: 'utf8' });
|
|
79
|
+
if (version.includes('Python 3')) {
|
|
80
|
+
const parsed = parsePythonVersion(version);
|
|
81
|
+
if (parsed) {
|
|
82
|
+
// Prefer compatible version (3.9-3.12)
|
|
83
|
+
if (isPythonVersionCompatible(parsed.major, parsed.minor)) {
|
|
84
|
+
return cmd;
|
|
85
|
+
}
|
|
86
|
+
// Keep track of first Python 3 found as fallback
|
|
87
|
+
if (!fallbackCmd) {
|
|
88
|
+
fallbackCmd = cmd;
|
|
89
|
+
fallbackVersion = parsed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Try next command
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If no compatible version found, use fallback with warning
|
|
99
|
+
if (fallbackCmd && fallbackVersion) {
|
|
100
|
+
console.warn(`[Python] Warning: Only Python ${fallbackVersion.major}.${fallbackVersion.minor} found, which may not be compatible with onnxruntime (requires 3.9-3.12).`);
|
|
101
|
+
console.warn('[Python] To use a specific Python version, set CCW_PYTHON environment variable:');
|
|
102
|
+
console.warn(' Windows: set CCW_PYTHON=C:\\path\\to\\python.exe');
|
|
103
|
+
console.warn(' Unix: export CCW_PYTHON=/path/to/python3.11');
|
|
104
|
+
console.warn('[Python] Alternatively, use LiteLLM embedding backend which has no Python version restrictions.');
|
|
105
|
+
return fallbackCmd;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new Error('Python 3 not found. Please install Python 3.9-3.12 and ensure it is in PATH, or set CCW_PYTHON environment variable.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the Python command for pip operations (uses -m pip for reliability)
|
|
113
|
+
* @returns Array of command arguments for spawn
|
|
114
|
+
*/
|
|
115
|
+
export function getPipCommand(): { pythonCmd: string; pipArgs: string[] } {
|
|
116
|
+
const pythonCmd = getSystemPython();
|
|
117
|
+
return {
|
|
118
|
+
pythonCmd,
|
|
119
|
+
pipArgs: ['-m', 'pip']
|
|
120
|
+
};
|
|
121
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
# codex-lens/src/codexlens/search/enrichment.py
|
|
2
|
-
"""Relationship enrichment for search results."""
|
|
3
|
-
import sqlite3
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import List, Dict, Any, Optional
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class RelationshipEnricher:
|
|
9
|
-
"""Enriches search results with code graph relationships."""
|
|
10
|
-
|
|
11
|
-
def __init__(self, index_path: Path):
|
|
12
|
-
"""Initialize with path to index database.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
index_path: Path to _index.db SQLite database
|
|
16
|
-
"""
|
|
17
|
-
self.index_path = index_path
|
|
18
|
-
self.db_conn: Optional[sqlite3.Connection] = None
|
|
19
|
-
self._connect()
|
|
20
|
-
|
|
21
|
-
def _connect(self) -> None:
|
|
22
|
-
"""Establish read-only database connection."""
|
|
23
|
-
if self.index_path.exists():
|
|
24
|
-
self.db_conn = sqlite3.connect(
|
|
25
|
-
f"file:{self.index_path}?mode=ro",
|
|
26
|
-
uri=True,
|
|
27
|
-
check_same_thread=False
|
|
28
|
-
)
|
|
29
|
-
self.db_conn.row_factory = sqlite3.Row
|
|
30
|
-
|
|
31
|
-
def enrich(self, results: List[Dict[str, Any]], limit: int = 10) -> List[Dict[str, Any]]:
|
|
32
|
-
"""Add relationship data to search results.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
results: List of search result dictionaries
|
|
36
|
-
limit: Maximum number of results to enrich
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Results with relationships field added
|
|
40
|
-
"""
|
|
41
|
-
if not self.db_conn:
|
|
42
|
-
return results
|
|
43
|
-
|
|
44
|
-
for result in results[:limit]:
|
|
45
|
-
file_path = result.get('file') or result.get('path')
|
|
46
|
-
symbol_name = result.get('symbol')
|
|
47
|
-
result['relationships'] = self._find_relationships(file_path, symbol_name)
|
|
48
|
-
return results
|
|
49
|
-
|
|
50
|
-
def _find_relationships(self, file_path: Optional[str], symbol_name: Optional[str]) -> List[Dict[str, Any]]:
|
|
51
|
-
"""Query relationships for a symbol.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
file_path: Path to file containing the symbol
|
|
55
|
-
symbol_name: Name of the symbol
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
List of relationship dictionaries with type, direction, target/source, file, line
|
|
59
|
-
"""
|
|
60
|
-
if not self.db_conn or not symbol_name:
|
|
61
|
-
return []
|
|
62
|
-
|
|
63
|
-
relationships = []
|
|
64
|
-
cursor = self.db_conn.cursor()
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
# Find symbol ID(s) by name and optionally file
|
|
68
|
-
if file_path:
|
|
69
|
-
cursor.execute(
|
|
70
|
-
'SELECT id FROM symbols WHERE name = ? AND file_path = ?',
|
|
71
|
-
(symbol_name, file_path)
|
|
72
|
-
)
|
|
73
|
-
else:
|
|
74
|
-
cursor.execute('SELECT id FROM symbols WHERE name = ?', (symbol_name,))
|
|
75
|
-
|
|
76
|
-
symbol_ids = [row[0] for row in cursor.fetchall()]
|
|
77
|
-
|
|
78
|
-
if not symbol_ids:
|
|
79
|
-
return []
|
|
80
|
-
|
|
81
|
-
# Query outgoing relationships (symbol is source)
|
|
82
|
-
placeholders = ','.join('?' * len(symbol_ids))
|
|
83
|
-
cursor.execute(f'''
|
|
84
|
-
SELECT sr.relationship_type, sr.target_symbol_fqn, sr.file_path, sr.line
|
|
85
|
-
FROM symbol_relationships sr
|
|
86
|
-
WHERE sr.source_symbol_id IN ({placeholders})
|
|
87
|
-
''', symbol_ids)
|
|
88
|
-
|
|
89
|
-
for row in cursor.fetchall():
|
|
90
|
-
relationships.append({
|
|
91
|
-
'type': row[0],
|
|
92
|
-
'direction': 'outgoing',
|
|
93
|
-
'target': row[1],
|
|
94
|
-
'file': row[2],
|
|
95
|
-
'line': row[3],
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
# Query incoming relationships (symbol is target)
|
|
99
|
-
# Match against symbol name or qualified name patterns
|
|
100
|
-
cursor.execute('''
|
|
101
|
-
SELECT sr.relationship_type, s.name AS source_name, sr.file_path, sr.line
|
|
102
|
-
FROM symbol_relationships sr
|
|
103
|
-
JOIN symbols s ON sr.source_symbol_id = s.id
|
|
104
|
-
WHERE sr.target_symbol_fqn = ? OR sr.target_symbol_fqn LIKE ?
|
|
105
|
-
''', (symbol_name, f'%.{symbol_name}'))
|
|
106
|
-
|
|
107
|
-
for row in cursor.fetchall():
|
|
108
|
-
rel_type = row[0]
|
|
109
|
-
# Convert to incoming type
|
|
110
|
-
incoming_type = self._to_incoming_type(rel_type)
|
|
111
|
-
relationships.append({
|
|
112
|
-
'type': incoming_type,
|
|
113
|
-
'direction': 'incoming',
|
|
114
|
-
'source': row[1],
|
|
115
|
-
'file': row[2],
|
|
116
|
-
'line': row[3],
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
except sqlite3.Error:
|
|
120
|
-
return []
|
|
121
|
-
|
|
122
|
-
return relationships
|
|
123
|
-
|
|
124
|
-
def _to_incoming_type(self, outgoing_type: str) -> str:
|
|
125
|
-
"""Convert outgoing relationship type to incoming type.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
outgoing_type: The outgoing relationship type (e.g., 'calls', 'imports')
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
Corresponding incoming type (e.g., 'called_by', 'imported_by')
|
|
132
|
-
"""
|
|
133
|
-
type_map = {
|
|
134
|
-
'calls': 'called_by',
|
|
135
|
-
'imports': 'imported_by',
|
|
136
|
-
'extends': 'extended_by',
|
|
137
|
-
}
|
|
138
|
-
return type_map.get(outgoing_type, f'{outgoing_type}_by')
|
|
139
|
-
|
|
140
|
-
def close(self) -> None:
|
|
141
|
-
"""Close database connection."""
|
|
142
|
-
if self.db_conn:
|
|
143
|
-
self.db_conn.close()
|
|
144
|
-
self.db_conn = None
|
|
145
|
-
|
|
146
|
-
def __enter__(self) -> 'RelationshipEnricher':
|
|
147
|
-
return self
|
|
148
|
-
|
|
149
|
-
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
150
|
-
self.close()
|
|
1
|
+
# codex-lens/src/codexlens/search/enrichment.py
|
|
2
|
+
"""Relationship enrichment for search results."""
|
|
3
|
+
import sqlite3
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RelationshipEnricher:
|
|
9
|
+
"""Enriches search results with code graph relationships."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, index_path: Path):
|
|
12
|
+
"""Initialize with path to index database.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
index_path: Path to _index.db SQLite database
|
|
16
|
+
"""
|
|
17
|
+
self.index_path = index_path
|
|
18
|
+
self.db_conn: Optional[sqlite3.Connection] = None
|
|
19
|
+
self._connect()
|
|
20
|
+
|
|
21
|
+
def _connect(self) -> None:
|
|
22
|
+
"""Establish read-only database connection."""
|
|
23
|
+
if self.index_path.exists():
|
|
24
|
+
self.db_conn = sqlite3.connect(
|
|
25
|
+
f"file:{self.index_path}?mode=ro",
|
|
26
|
+
uri=True,
|
|
27
|
+
check_same_thread=False
|
|
28
|
+
)
|
|
29
|
+
self.db_conn.row_factory = sqlite3.Row
|
|
30
|
+
|
|
31
|
+
def enrich(self, results: List[Dict[str, Any]], limit: int = 10) -> List[Dict[str, Any]]:
|
|
32
|
+
"""Add relationship data to search results.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
results: List of search result dictionaries
|
|
36
|
+
limit: Maximum number of results to enrich
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Results with relationships field added
|
|
40
|
+
"""
|
|
41
|
+
if not self.db_conn:
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
for result in results[:limit]:
|
|
45
|
+
file_path = result.get('file') or result.get('path')
|
|
46
|
+
symbol_name = result.get('symbol')
|
|
47
|
+
result['relationships'] = self._find_relationships(file_path, symbol_name)
|
|
48
|
+
return results
|
|
49
|
+
|
|
50
|
+
def _find_relationships(self, file_path: Optional[str], symbol_name: Optional[str]) -> List[Dict[str, Any]]:
|
|
51
|
+
"""Query relationships for a symbol.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
file_path: Path to file containing the symbol
|
|
55
|
+
symbol_name: Name of the symbol
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of relationship dictionaries with type, direction, target/source, file, line
|
|
59
|
+
"""
|
|
60
|
+
if not self.db_conn or not symbol_name:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
relationships = []
|
|
64
|
+
cursor = self.db_conn.cursor()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Find symbol ID(s) by name and optionally file
|
|
68
|
+
if file_path:
|
|
69
|
+
cursor.execute(
|
|
70
|
+
'SELECT id FROM symbols WHERE name = ? AND file_path = ?',
|
|
71
|
+
(symbol_name, file_path)
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
cursor.execute('SELECT id FROM symbols WHERE name = ?', (symbol_name,))
|
|
75
|
+
|
|
76
|
+
symbol_ids = [row[0] for row in cursor.fetchall()]
|
|
77
|
+
|
|
78
|
+
if not symbol_ids:
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
# Query outgoing relationships (symbol is source)
|
|
82
|
+
placeholders = ','.join('?' * len(symbol_ids))
|
|
83
|
+
cursor.execute(f'''
|
|
84
|
+
SELECT sr.relationship_type, sr.target_symbol_fqn, sr.file_path, sr.line
|
|
85
|
+
FROM symbol_relationships sr
|
|
86
|
+
WHERE sr.source_symbol_id IN ({placeholders})
|
|
87
|
+
''', symbol_ids)
|
|
88
|
+
|
|
89
|
+
for row in cursor.fetchall():
|
|
90
|
+
relationships.append({
|
|
91
|
+
'type': row[0],
|
|
92
|
+
'direction': 'outgoing',
|
|
93
|
+
'target': row[1],
|
|
94
|
+
'file': row[2],
|
|
95
|
+
'line': row[3],
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
# Query incoming relationships (symbol is target)
|
|
99
|
+
# Match against symbol name or qualified name patterns
|
|
100
|
+
cursor.execute('''
|
|
101
|
+
SELECT sr.relationship_type, s.name AS source_name, sr.file_path, sr.line
|
|
102
|
+
FROM symbol_relationships sr
|
|
103
|
+
JOIN symbols s ON sr.source_symbol_id = s.id
|
|
104
|
+
WHERE sr.target_symbol_fqn = ? OR sr.target_symbol_fqn LIKE ?
|
|
105
|
+
''', (symbol_name, f'%.{symbol_name}'))
|
|
106
|
+
|
|
107
|
+
for row in cursor.fetchall():
|
|
108
|
+
rel_type = row[0]
|
|
109
|
+
# Convert to incoming type
|
|
110
|
+
incoming_type = self._to_incoming_type(rel_type)
|
|
111
|
+
relationships.append({
|
|
112
|
+
'type': incoming_type,
|
|
113
|
+
'direction': 'incoming',
|
|
114
|
+
'source': row[1],
|
|
115
|
+
'file': row[2],
|
|
116
|
+
'line': row[3],
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
except sqlite3.Error:
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
return relationships
|
|
123
|
+
|
|
124
|
+
def _to_incoming_type(self, outgoing_type: str) -> str:
|
|
125
|
+
"""Convert outgoing relationship type to incoming type.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
outgoing_type: The outgoing relationship type (e.g., 'calls', 'imports')
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Corresponding incoming type (e.g., 'called_by', 'imported_by')
|
|
132
|
+
"""
|
|
133
|
+
type_map = {
|
|
134
|
+
'calls': 'called_by',
|
|
135
|
+
'imports': 'imported_by',
|
|
136
|
+
'extends': 'extended_by',
|
|
137
|
+
}
|
|
138
|
+
return type_map.get(outgoing_type, f'{outgoing_type}_by')
|
|
139
|
+
|
|
140
|
+
def close(self) -> None:
|
|
141
|
+
"""Close database connection."""
|
|
142
|
+
if self.db_conn:
|
|
143
|
+
self.db_conn.close()
|
|
144
|
+
self.db_conn = None
|
|
145
|
+
|
|
146
|
+
def __enter__(self) -> 'RelationshipEnricher':
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
150
|
+
self.close()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-workflow",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.18",
|
|
4
4
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ccw/src/index.js",
|