-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfetch.py
More file actions
211 lines (165 loc) · 7.13 KB
/
Copy pathfetch.py
File metadata and controls
211 lines (165 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/usr/bin/env python3
"""
CodeAlive Fetch - Retrieve full content for code artifacts
Usage:
python fetch.py <identifier1> [identifier2...] [--data-source NAME_OR_ID]
Examples:
# Fetch a single artifact (symbol)
python fetch.py "my-org/backend::src/services/auth.py::AuthService.validate_token(token: str)"
# Fetch a file
python fetch.py "my-org/backend::src/services/auth.py"
# Fetch multiple artifacts
python fetch.py "my-org/backend::src/auth.py::login" "my-org/backend::src/utils.py::helper"
# Disambiguate an identifier that exists in more than one data source
# (use the dataSource name or id from a search result)
python fetch.py "my-org/backend::src/auth.py::login" --data-source "backend"
Identifiers come from semantic/grep search results (the `identifier` field).
The format is: {owner/repo}::{path}::{symbol} (for symbols/chunks)
{owner/repo}::{path} (for files)
Pass --data-source (a data source Name or Id from a search result's `dataSource`)
to disambiguate an identifier that exists in more than one data source. Without it,
an ambiguous identifier returns a 409 listing the candidate data sources.
Maximum 20 identifiers per request.
"""
import sys
import json
from pathlib import Path
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent / "lib"))
from api_client import CodeAliveClient
def _add_line_numbers(content: str, start_line: int = 1) -> str:
"""Add line numbers to content for easier navigation."""
if not content:
return content
lines = content.split("\n")
width = len(str(start_line + len(lines) - 1))
numbered = [f"{start_line + i:>{width}} | {line}" for i, line in enumerate(lines)]
return "\n".join(numbered)
def _has_any_calls(relationships: dict) -> bool:
"""True if a relationships preview has at least one outgoing/incoming call."""
for key in ("outgoingCallsCount", "incomingCallsCount"):
count = relationships.get(key)
if count and count > 0:
return True
return False
def _format_relationships_preview(relationships: dict) -> list:
"""Format the inline preview of call relationships returned with each artifact.
Returns a list of output lines (possibly empty).
"""
lines: list = []
for direction, key, label in (
("outgoing", "outgoingCalls", "↗ outgoing_calls"),
("incoming", "incomingCalls", "↙ incoming_calls"),
):
count = relationships.get(f"{key}Count")
if count is None:
continue
calls = relationships.get(key) or []
lines.append(f" {label} ({count}):")
if not calls:
lines.append(" (none in preview)")
continue
for call in calls:
ident = call.get("identifier", "")
summary = call.get("summary")
if summary:
lines.append(f" • {ident}")
lines.append(f" 📝 {summary}")
else:
lines.append(f" • {ident}")
return lines
def _data_source_miss_hint(data_source: str) -> str:
"""Recovery hint when a data-source-scoped fetch returns nothing."""
return (
f'\n💡 Hint: nothing was found in data source "{data_source}". The identifier may belong to a '
"different data source, or the --data-source value may be wrong. Try: re-run with --data-source "
"set to a different candidate (use the Source name or id from your search results, or run "
"datasources.py), or drop --data-source entirely — an ambiguous identifier then returns a 409 "
"listing the candidate data sources to choose from."
)
def format_artifacts(data: dict, data_source: str = None) -> str:
"""Format fetched artifacts for display."""
artifacts = data.get("artifacts", [])
if not artifacts:
msg = "No artifacts returned."
return msg + _data_source_miss_hint(data_source) if data_source else msg
output = []
count = 0
has_any_relationships = False
for artifact in artifacts:
content = artifact.get("content")
if content is None:
continue
count += 1
identifier = artifact.get("identifier", "unknown")
content_byte_size = artifact.get("contentByteSize")
size_str = f" ({content_byte_size} bytes)" if content_byte_size else ""
output.append(f"\n{'='*60}")
output.append(f"📄 {identifier}{size_str}")
output.append(f"{'='*60}")
start_line = artifact.get("startLine") or 1
output.append(_add_line_numbers(content, start_line))
relationships = artifact.get("relationships")
if relationships is not None:
preview_lines = _format_relationships_preview(relationships)
if preview_lines:
output.append("\n--- relationships (preview) ---")
output.extend(preview_lines)
if _has_any_calls(relationships):
has_any_relationships = True
if not output:
msg = "No artifacts found."
return msg + _data_source_miss_hint(data_source) if data_source else msg
output.append(f"\n({count} artifact(s))")
if has_any_relationships:
output.append(
"\n💡 Hint: the relationships shown above are a preview (up to 3 calls "
"per direction).\n"
" To see the full call graph, inheritance, or references for an "
"artifact, run:\n"
" python relationships.py <identifier> "
"[--profile callsOnly|inheritanceOnly|allRelevant|referencesOnly]"
)
return "\n".join(output)
def main():
"""CLI interface for fetching artifacts."""
if len(sys.argv) < 2 or sys.argv[1] == "--help":
print(__doc__)
if len(sys.argv) < 2:
sys.exit(1)
sys.exit(0)
identifiers = []
data_source = None
i = 1
while i < len(sys.argv):
arg = sys.argv[i]
if arg == "--data-source":
# Match the flag first, then require a value — otherwise a trailing "--data-source"
# with no value would be silently appended as an identifier.
if i + 1 >= len(sys.argv):
print("Error: --data-source requires a value.", file=sys.stderr)
sys.exit(1)
data_source = sys.argv[i + 1]
i += 2
else:
identifiers.append(arg)
i += 1
if not identifiers:
print("Error: At least one identifier is required.", file=sys.stderr)
sys.exit(1)
if len(identifiers) > 20:
print("Error: Maximum 20 identifiers per request.", file=sys.stderr)
sys.exit(1)
try:
client = CodeAliveClient()
print(f"📥 Fetching {len(identifiers)} artifact(s)", file=sys.stderr)
if data_source:
print(f" data source: {data_source}", file=sys.stderr)
print(file=sys.stderr)
result = client.fetch_artifacts(identifiers=identifiers, data_source=data_source)
print(format_artifacts(result, data_source=data_source))
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()