code-puppy 0.0.86__py3-none-any.whl → 0.0.88__py3-none-any.whl
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.
- code_puppy/agent_prompts.py +1 -1
- code_puppy/command_line/meta_command_handler.py +0 -16
- code_puppy/command_line/prompt_toolkit_completion.py +6 -2
- code_puppy/main.py +7 -0
- code_puppy/message_history_processor.py +1 -1
- code_puppy/token_utils.py +70 -0
- code_puppy/tools/common.py +31 -1
- code_puppy/tools/file_operations.py +47 -15
- code_puppy/tools/token_check.py +11 -0
- {code_puppy-0.0.86.dist-info → code_puppy-0.0.88.dist-info}/METADATA +1 -1
- {code_puppy-0.0.86.dist-info → code_puppy-0.0.88.dist-info}/RECORD +15 -14
- code_puppy/tools/ts_code_map.py +0 -515
- {code_puppy-0.0.86.data → code_puppy-0.0.88.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.86.dist-info → code_puppy-0.0.88.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.86.dist-info → code_puppy-0.0.88.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.86.dist-info → code_puppy-0.0.88.dist-info}/licenses/LICENSE +0 -0
code_puppy/agent_prompts.py
CHANGED
|
@@ -26,7 +26,7 @@ YOU MUST USE THESE TOOLS to complete tasks (do not just describe what should be
|
|
|
26
26
|
|
|
27
27
|
File Operations:
|
|
28
28
|
- list_files(directory=".", recursive=True): ALWAYS use this to explore directories before trying to read/modify files
|
|
29
|
-
- read_file(file_path): ALWAYS use this to read existing files before modifying them.
|
|
29
|
+
- read_file(file_path: str, start_line: int | None = None, num_lines: int | None = None): ALWAYS use this to read existing files before modifying them. By default, read the entire file. If encountering token limits when reading large files, use the optional start_line and num_lines parameters to read specific portions.
|
|
30
30
|
- edit_file(path, diff): Use this single tool to create new files, overwrite entire files, perform targeted replacements, or delete snippets depending on the JSON/raw payload provided.
|
|
31
31
|
- delete_file(file_path): Use this to remove files when needed
|
|
32
32
|
- grep(search_string, directory="."): Use this to recursively search for a string across files starting from the specified directory, capping results at 200 matches.
|
|
@@ -14,7 +14,6 @@ META_COMMANDS_HELP = """
|
|
|
14
14
|
[bold magenta]Meta Commands Help[/bold magenta]
|
|
15
15
|
~help, ~h Show this help message
|
|
16
16
|
~cd <dir> Change directory or show directories
|
|
17
|
-
~codemap <dir> Show code structure for <dir>
|
|
18
17
|
~m <model> Set active model
|
|
19
18
|
~motd Show the latest message of the day (MOTD)
|
|
20
19
|
~show Show puppy config key-values
|
|
@@ -34,21 +33,6 @@ def handle_meta_command(command: str, console: Console) -> bool:
|
|
|
34
33
|
print_motd(console, force=True)
|
|
35
34
|
return True
|
|
36
35
|
|
|
37
|
-
# ~codemap (code structure visualization)
|
|
38
|
-
if command.startswith("~codemap"):
|
|
39
|
-
from code_puppy.tools.ts_code_map import make_code_map
|
|
40
|
-
|
|
41
|
-
tokens = command.split()
|
|
42
|
-
if len(tokens) > 1:
|
|
43
|
-
target_dir = os.path.expanduser(tokens[1])
|
|
44
|
-
else:
|
|
45
|
-
target_dir = os.getcwd()
|
|
46
|
-
try:
|
|
47
|
-
make_code_map(target_dir, ignore_tests=True)
|
|
48
|
-
except Exception as e:
|
|
49
|
-
console.print(f"[red]Error generating code map:[/red] {e}")
|
|
50
|
-
return True
|
|
51
|
-
|
|
52
36
|
if command.startswith("~cd"):
|
|
53
37
|
tokens = command.split()
|
|
54
38
|
if len(tokens) == 1:
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
# ANSI color codes are no longer necessary because prompt_toolkit handles
|
|
2
6
|
# styling via the `Style` class. We keep them here commented-out in case
|
|
3
7
|
# someone needs raw ANSI later, but they are unused in the current code.
|
|
@@ -171,7 +175,7 @@ async def get_input_with_combined_completion(
|
|
|
171
175
|
def _(event):
|
|
172
176
|
event.app.current_buffer.insert_text("\n")
|
|
173
177
|
|
|
174
|
-
@bindings.add(
|
|
178
|
+
@bindings.add('c-c')
|
|
175
179
|
def _(event):
|
|
176
180
|
"""Cancel the current prompt when the user presses the ESC key alone."""
|
|
177
181
|
event.app.exit(exception=KeyboardInterrupt)
|
|
@@ -222,4 +226,4 @@ if __name__ == "__main__":
|
|
|
222
226
|
break
|
|
223
227
|
print("\nGoodbye!")
|
|
224
228
|
|
|
225
|
-
asyncio.run(main())
|
|
229
|
+
asyncio.run(main())
|
code_puppy/main.py
CHANGED
|
@@ -232,12 +232,19 @@ async def interactive_mode(history_file_path: str) -> None:
|
|
|
232
232
|
local_cancelled = True
|
|
233
233
|
except Exception as e:
|
|
234
234
|
console.print(f"[dim]Shell kill error: {e}[/dim]")
|
|
235
|
+
# On Windows, we need to reset the signal handler to avoid weird terminal behavior
|
|
236
|
+
if sys.platform.startswith("win"):
|
|
237
|
+
signal.signal(signal.SIGINT, original_handler or signal.SIG_DFL)
|
|
235
238
|
try:
|
|
236
239
|
original_handler = signal.getsignal(signal.SIGINT)
|
|
237
240
|
signal.signal(signal.SIGINT, keyboard_interrupt_handler)
|
|
238
241
|
result = await agent_task
|
|
239
242
|
except asyncio.CancelledError:
|
|
240
243
|
pass
|
|
244
|
+
except KeyboardInterrupt:
|
|
245
|
+
# Handle Ctrl+C from terminal
|
|
246
|
+
keyboard_interrupt_handler(signal.SIGINT, None)
|
|
247
|
+
raise
|
|
241
248
|
finally:
|
|
242
249
|
if original_handler:
|
|
243
250
|
signal.signal(signal.SIGINT, original_handler)
|
|
@@ -250,7 +250,7 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
250
250
|
[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f}
|
|
251
251
|
""")
|
|
252
252
|
|
|
253
|
-
if proportion_used > 0.
|
|
253
|
+
if proportion_used > 0.85:
|
|
254
254
|
summary = summarize_messages(messages)
|
|
255
255
|
result_messages = [messages[0], summary]
|
|
256
256
|
final_token_count = sum(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import tiktoken
|
|
3
|
+
|
|
4
|
+
import pydantic
|
|
5
|
+
from pydantic_ai.messages import ModelMessage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_tokenizer():
|
|
9
|
+
"""
|
|
10
|
+
Always use cl100k_base tokenizer regardless of model type.
|
|
11
|
+
This is a simple approach that works reasonably well for most models.
|
|
12
|
+
"""
|
|
13
|
+
return tiktoken.get_encoding("cl100k_base")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def stringify_message_part(part) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Convert a message part to a string representation for token estimation or other uses.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
part: A message part that may contain content or be a tool call
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
String representation of the message part
|
|
25
|
+
"""
|
|
26
|
+
result = ""
|
|
27
|
+
if hasattr(part, "part_kind"):
|
|
28
|
+
result += part.part_kind + ": "
|
|
29
|
+
else:
|
|
30
|
+
result += str(type(part)) + ": "
|
|
31
|
+
|
|
32
|
+
# Handle content
|
|
33
|
+
if hasattr(part, "content") and part.content:
|
|
34
|
+
# Handle different content types
|
|
35
|
+
if isinstance(part.content, str):
|
|
36
|
+
result = part.content
|
|
37
|
+
elif isinstance(part.content, pydantic.BaseModel):
|
|
38
|
+
result = json.dumps(part.content.model_dump())
|
|
39
|
+
elif isinstance(part.content, dict):
|
|
40
|
+
result = json.dumps(part.content)
|
|
41
|
+
else:
|
|
42
|
+
result = str(part.content)
|
|
43
|
+
|
|
44
|
+
# Handle tool calls which may have additional token costs
|
|
45
|
+
# If part also has content, we'll process tool calls separately
|
|
46
|
+
if hasattr(part, "tool_name") and part.tool_name:
|
|
47
|
+
# Estimate tokens for tool name and parameters
|
|
48
|
+
tool_text = part.tool_name
|
|
49
|
+
if hasattr(part, "args"):
|
|
50
|
+
tool_text += f" {str(part.args)}"
|
|
51
|
+
result += tool_text
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
57
|
+
"""
|
|
58
|
+
Estimate the number of tokens in a message using tiktoken with cl100k_base encoding.
|
|
59
|
+
This is more accurate than character-based estimation.
|
|
60
|
+
"""
|
|
61
|
+
tokenizer = get_tokenizer()
|
|
62
|
+
total_tokens = 0
|
|
63
|
+
|
|
64
|
+
for part in message.parts:
|
|
65
|
+
part_str = stringify_message_part(part)
|
|
66
|
+
if part_str:
|
|
67
|
+
tokens = tokenizer.encode(part_str)
|
|
68
|
+
total_tokens += len(tokens)
|
|
69
|
+
|
|
70
|
+
return max(1, total_tokens)
|
code_puppy/tools/common.py
CHANGED
|
@@ -2,14 +2,44 @@ import os
|
|
|
2
2
|
import fnmatch
|
|
3
3
|
|
|
4
4
|
from typing import Optional, Tuple
|
|
5
|
-
|
|
5
|
+
import tiktoken
|
|
6
6
|
from rapidfuzz.distance import JaroWinkler
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
|
+
# get_model_context_length will be imported locally where needed to avoid circular imports
|
|
10
|
+
|
|
9
11
|
NO_COLOR = bool(int(os.environ.get("CODE_PUPPY_NO_COLOR", "0")))
|
|
10
12
|
console = Console(no_color=NO_COLOR)
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def get_model_context_length() -> int:
|
|
16
|
+
"""
|
|
17
|
+
Get the context length for the currently configured model from models.json
|
|
18
|
+
"""
|
|
19
|
+
# Import locally to avoid circular imports
|
|
20
|
+
from code_puppy.model_factory import ModelFactory
|
|
21
|
+
from code_puppy.config import get_model_name
|
|
22
|
+
import os
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# Load model configuration
|
|
26
|
+
models_path = os.environ.get("MODELS_JSON_PATH")
|
|
27
|
+
if not models_path:
|
|
28
|
+
models_path = Path(__file__).parent.parent / "models.json"
|
|
29
|
+
else:
|
|
30
|
+
models_path = Path(models_path)
|
|
31
|
+
|
|
32
|
+
model_configs = ModelFactory.load_config(str(models_path))
|
|
33
|
+
model_name = get_model_name()
|
|
34
|
+
|
|
35
|
+
# Get context length from model config
|
|
36
|
+
model_config = model_configs.get(model_name, {})
|
|
37
|
+
context_length = model_config.get("context_length", 128000) # Default value
|
|
38
|
+
|
|
39
|
+
# Reserve 10% of context for response
|
|
40
|
+
return int(context_length)
|
|
41
|
+
|
|
42
|
+
|
|
13
43
|
# -------------------
|
|
14
44
|
# Shared ignore patterns/helpers
|
|
15
45
|
# -------------------
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import List
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel
|
|
6
|
+
from pydantic import BaseModel, conint
|
|
7
7
|
from pydantic_ai import RunContext
|
|
8
8
|
|
|
9
9
|
from code_puppy.tools.common import console
|
|
10
|
-
|
|
10
|
+
from code_puppy.token_utils import get_tokenizer
|
|
11
|
+
from code_puppy.tools.token_check import token_guard
|
|
11
12
|
# ---------------------------------------------------------------------------
|
|
12
13
|
# Module-level helper functions (exposed for unit tests _and_ used as tools)
|
|
13
14
|
# ---------------------------------------------------------------------------
|
|
@@ -180,24 +181,55 @@ def _list_files(
|
|
|
180
181
|
|
|
181
182
|
class ReadFileOutput(BaseModel):
|
|
182
183
|
content: str | None
|
|
184
|
+
num_tokens: conint(lt=10000)
|
|
185
|
+
error: str | None = None
|
|
183
186
|
|
|
184
187
|
|
|
185
|
-
def _read_file(context: RunContext, file_path: str) -> ReadFileOutput:
|
|
188
|
+
def _read_file(context: RunContext, file_path: str, start_line: int | None = None, num_lines: int | None = None) -> ReadFileOutput:
|
|
186
189
|
file_path = os.path.abspath(file_path)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
|
|
191
|
+
# Build console message with optional parameters
|
|
192
|
+
console_msg = f"\n[bold white on blue] READ FILE [/bold white on blue] \U0001f4c2 [bold cyan]{file_path}[/bold cyan]"
|
|
193
|
+
if start_line is not None and num_lines is not None:
|
|
194
|
+
console_msg += f" [dim](lines {start_line}-{start_line + num_lines - 1})[/dim]"
|
|
195
|
+
console.print(console_msg)
|
|
196
|
+
|
|
190
197
|
console.print("[dim]" + "-" * 60 + "[/dim]")
|
|
191
198
|
if not os.path.exists(file_path):
|
|
192
|
-
|
|
199
|
+
error_msg = f"File {file_path} does not exist"
|
|
200
|
+
return ReadFileOutput(content=error_msg, num_tokens=0, error=error_msg)
|
|
193
201
|
if not os.path.isfile(file_path):
|
|
194
|
-
|
|
202
|
+
error_msg = f"{file_path} is not a file"
|
|
203
|
+
return ReadFileOutput(content=error_msg, num_tokens=0, error=error_msg)
|
|
195
204
|
try:
|
|
196
205
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
if start_line is not None and num_lines is not None:
|
|
207
|
+
# Read only the specified lines
|
|
208
|
+
lines = f.readlines()
|
|
209
|
+
# Adjust for 1-based line numbering
|
|
210
|
+
start_idx = start_line - 1
|
|
211
|
+
end_idx = start_idx + num_lines
|
|
212
|
+
# Ensure indices are within bounds
|
|
213
|
+
start_idx = max(0, start_idx)
|
|
214
|
+
end_idx = min(len(lines), end_idx)
|
|
215
|
+
content = ''.join(lines[start_idx:end_idx])
|
|
216
|
+
else:
|
|
217
|
+
# Read the entire file
|
|
218
|
+
content = f.read()
|
|
219
|
+
|
|
220
|
+
tokenizer = get_tokenizer()
|
|
221
|
+
num_tokens = len(tokenizer.encode(content))
|
|
222
|
+
if num_tokens > 10000:
|
|
223
|
+
raise ValueError("The file is massive, greater than 10,000 tokens which is dangerous to read entirely. Please read this file in chunks.")
|
|
224
|
+
token_guard(num_tokens)
|
|
225
|
+
return ReadFileOutput(content=content, num_tokens=num_tokens)
|
|
226
|
+
except (FileNotFoundError, PermissionError):
|
|
227
|
+
# For backward compatibility with tests, return "FILE NOT FOUND" for these specific errors
|
|
228
|
+
error_msg = "FILE NOT FOUND"
|
|
229
|
+
return ReadFileOutput(content=error_msg, num_tokens=0, error=error_msg)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
message = f"An error occurred trying to read the file: {e}"
|
|
232
|
+
return ReadFileOutput(content=message, num_tokens=0, error=message)
|
|
201
233
|
|
|
202
234
|
|
|
203
235
|
class MatchInfo(BaseModel):
|
|
@@ -238,7 +270,7 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
238
270
|
**{
|
|
239
271
|
"file_path": file_path,
|
|
240
272
|
"line_number": line_number,
|
|
241
|
-
"line_content": line_content.
|
|
273
|
+
"line_content": line_content.rstrip("\n\r"),
|
|
242
274
|
}
|
|
243
275
|
)
|
|
244
276
|
matches.append(match_info)
|
|
@@ -282,8 +314,8 @@ def list_files(
|
|
|
282
314
|
return _list_files(context, directory, recursive)
|
|
283
315
|
|
|
284
316
|
|
|
285
|
-
def read_file(context: RunContext, file_path: str = "") -> ReadFileOutput:
|
|
286
|
-
return _read_file(context, file_path)
|
|
317
|
+
def read_file(context: RunContext, file_path: str = "", start_line: int | None = None, num_lines: int | None = None) -> ReadFileOutput:
|
|
318
|
+
return _read_file(context, file_path, start_line, num_lines)
|
|
287
319
|
|
|
288
320
|
|
|
289
321
|
def grep(
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from code_puppy.tools.common import get_model_context_length
|
|
2
|
+
from code_puppy.token_utils import estimate_tokens_for_message
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def token_guard(num_tokens: int):
|
|
6
|
+
from code_puppy import state_management
|
|
7
|
+
current_history = state_management.get_message_history()
|
|
8
|
+
message_hist_tokens = sum(estimate_tokens_for_message(msg) for msg in current_history)
|
|
9
|
+
|
|
10
|
+
if message_hist_tokens + num_tokens > (get_model_context_length() * 0.9):
|
|
11
|
+
raise ValueError("Tokens produced by this tool call would exceed model capacity")
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
code_puppy/__init__.py,sha256=CWH46ZAmJRmHAbOiAhG07OrWYEcEt4yvDTkZU341Wag,169
|
|
2
2
|
code_puppy/agent.py,sha256=7_1FpGPnw8U632OXP0hLmFIozfVvllF491q8gCpaa8c,3284
|
|
3
|
-
code_puppy/agent_prompts.py,sha256=
|
|
3
|
+
code_puppy/agent_prompts.py,sha256=t3-lqDKrDxCKxFa_va4Suze9BT-JOu1dh9iGiAVNFO4,6828
|
|
4
4
|
code_puppy/config.py,sha256=r5nw5ChOP8xd_K5yo8U5OtO2gy2bFhARiyNtDp1JrwQ,5013
|
|
5
|
-
code_puppy/main.py,sha256=
|
|
6
|
-
code_puppy/message_history_processor.py,sha256=
|
|
5
|
+
code_puppy/main.py,sha256=NLA6TI2is3htFRc5BGTwF4Xp68EtnN-6nOExD9D8i_g,11513
|
|
6
|
+
code_puppy/message_history_processor.py,sha256=2k9y4wXkyZ9v4XLpfH6E3TKLzf303i-k1hXBE-aIf9U,9249
|
|
7
7
|
code_puppy/model_factory.py,sha256=HXuFHNkVjkCcorAd3ScFmSvBILO932UTq6OmNAqisT8,10898
|
|
8
8
|
code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
|
|
9
9
|
code_puppy/state_management.py,sha256=JkTkmq6f9rl_RHPDoBqJvbAzgaMsIkJf-k38ragItIo,1692
|
|
10
10
|
code_puppy/summarization_agent.py,sha256=jHUQe6iYJsMT0ywEwO7CrhUIKEamO5imhAsDwvNuvow,2684
|
|
11
|
+
code_puppy/token_utils.py,sha256=g7Jj6NAy_a2ab7BXpwyhktruR-QlUV670H_mCPZV1N4,2110
|
|
11
12
|
code_puppy/version_checker.py,sha256=aRGulzuY4C4CdFvU1rITduyL-1xTFsn4GiD1uSfOl_Y,396
|
|
12
13
|
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
|
13
14
|
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
|
14
|
-
code_puppy/command_line/meta_command_handler.py,sha256=
|
|
15
|
+
code_puppy/command_line/meta_command_handler.py,sha256=d5eVWzRoThYD3cR-GS0CMwHxDvBK4ezLdSIqwWDrq0g,5620
|
|
15
16
|
code_puppy/command_line/model_picker_completion.py,sha256=NkyZZG7IhcVWSJ3ADytwCA5f8DpNeVs759Qtqs4fQtY,3733
|
|
16
17
|
code_puppy/command_line/motd.py,sha256=FoZsiVpXGF8WpAmEJX4O895W7MDuzCtNWvFAOShxUXY,1572
|
|
17
|
-
code_puppy/command_line/prompt_toolkit_completion.py,sha256=
|
|
18
|
+
code_puppy/command_line/prompt_toolkit_completion.py,sha256=De_grHDPOvCRph-HDOGCSgX6r_q6akoXArTRwgAskLU,8334
|
|
18
19
|
code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
|
|
19
20
|
code_puppy/tools/__init__.py,sha256=WTHYIfRk2KMmk6o45TELpbB3GIiAm8s7GmfJ7Zy_tww,503
|
|
20
21
|
code_puppy/tools/command_runner.py,sha256=9UWCSPpuEndaPx8Ecc8TRsn3rMHNd2AqerirvYPGRIw,14358
|
|
21
|
-
code_puppy/tools/common.py,sha256=
|
|
22
|
+
code_puppy/tools/common.py,sha256=UkhnfLG1bmd4f9nZCcmno088AtKtAnEES1tydxUN-Fk,3265
|
|
22
23
|
code_puppy/tools/file_modifications.py,sha256=BzQrGEacS2NZr2ru9N30x_Qd70JDudBKOAPO1XjBohg,13861
|
|
23
|
-
code_puppy/tools/file_operations.py,sha256=
|
|
24
|
-
code_puppy/tools/
|
|
25
|
-
code_puppy-0.0.
|
|
26
|
-
code_puppy-0.0.
|
|
27
|
-
code_puppy-0.0.
|
|
28
|
-
code_puppy-0.0.
|
|
29
|
-
code_puppy-0.0.
|
|
30
|
-
code_puppy-0.0.
|
|
24
|
+
code_puppy/tools/file_operations.py,sha256=hXOoRWhOyUyVEX_uAah8VBMZr5AsuyhMvj9uL8osUpE,13257
|
|
25
|
+
code_puppy/tools/token_check.py,sha256=F3eygdI8fgb6dfCrSkGw_OLI7cb_Kpa5ILft4BQ7hvY,525
|
|
26
|
+
code_puppy-0.0.88.data/data/code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
|
|
27
|
+
code_puppy-0.0.88.dist-info/METADATA,sha256=oKP59tXivzgNzTHFCWRbxVr7otqp5URAZ96y0cHSQHc,6351
|
|
28
|
+
code_puppy-0.0.88.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
29
|
+
code_puppy-0.0.88.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
30
|
+
code_puppy-0.0.88.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
31
|
+
code_puppy-0.0.88.dist-info/RECORD,,
|
code_puppy/tools/ts_code_map.py
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from code_puppy.tools.common import should_ignore_path
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from rich.text import Text
|
|
5
|
-
from rich.tree import Tree as RichTree
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
from tree_sitter_language_pack import get_parser
|
|
8
|
-
|
|
9
|
-
from functools import partial, wraps
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _f(fmt): # helper to keep the table tidy
|
|
13
|
-
return lambda name, _fmt=fmt: _fmt.format(name=name)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def mark_export(label_fn, default=False):
|
|
17
|
-
"""Decorator to prefix 'export ' (or 'export default ') when requested."""
|
|
18
|
-
|
|
19
|
-
@wraps(label_fn)
|
|
20
|
-
def _wrap(name, *, exported=False):
|
|
21
|
-
prefix = "export default " if default else "export " if exported else ""
|
|
22
|
-
return prefix + label_fn(name)
|
|
23
|
-
|
|
24
|
-
return _wrap
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
LANGS = {
|
|
28
|
-
".py": {
|
|
29
|
-
"lang": "python",
|
|
30
|
-
"name_field": "name",
|
|
31
|
-
"nodes": {
|
|
32
|
-
"function_definition": partial(_f("def {name}()"), style="green"),
|
|
33
|
-
"class_definition": partial(_f("class {name}"), style="magenta"),
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
".rb": {
|
|
37
|
-
"lang": "ruby",
|
|
38
|
-
"name_field": "name",
|
|
39
|
-
"nodes": {
|
|
40
|
-
"method": partial(_f("def {name}"), style="green"),
|
|
41
|
-
"class": partial(_f("class {name}"), style="magenta"),
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
".php": {
|
|
45
|
-
"lang": "php",
|
|
46
|
-
"name_field": "name",
|
|
47
|
-
"nodes": {
|
|
48
|
-
"function_definition": partial(_f("function {name}()"), style="green"),
|
|
49
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
".lua": {
|
|
53
|
-
"lang": "lua",
|
|
54
|
-
"name_field": "name",
|
|
55
|
-
"nodes": {
|
|
56
|
-
"function_declaration": partial(_f("function {name}()"), style="green")
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
".pl": {
|
|
60
|
-
"lang": "perl",
|
|
61
|
-
"name_field": "name",
|
|
62
|
-
"nodes": {"sub_definition": partial(_f("sub {name}()"), style="green")},
|
|
63
|
-
},
|
|
64
|
-
".r": {
|
|
65
|
-
"lang": "r",
|
|
66
|
-
"name_field": "name",
|
|
67
|
-
"nodes": {"function_definition": partial(_f("func {name}()"), style="green")},
|
|
68
|
-
},
|
|
69
|
-
".js": {
|
|
70
|
-
"lang": "javascript",
|
|
71
|
-
"name_field": "name",
|
|
72
|
-
"nodes": {
|
|
73
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
74
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
75
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
76
|
-
"export_default_statement": partial(
|
|
77
|
-
_f("export default {name}"), style="yellow"
|
|
78
|
-
),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
".mjs": {
|
|
82
|
-
"lang": "javascript",
|
|
83
|
-
"name_field": "name",
|
|
84
|
-
"nodes": {
|
|
85
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
86
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
87
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
88
|
-
"export_default_statement": partial(
|
|
89
|
-
_f("export default {name}"), style="yellow"
|
|
90
|
-
),
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
".cjs": {
|
|
94
|
-
"lang": "javascript",
|
|
95
|
-
"name_field": "name",
|
|
96
|
-
"nodes": {
|
|
97
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
98
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
99
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
100
|
-
"export_default_statement": partial(
|
|
101
|
-
_f("export default {name}"), style="yellow"
|
|
102
|
-
),
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
".jsx": {
|
|
106
|
-
"lang": "jsx",
|
|
107
|
-
"name_field": None,
|
|
108
|
-
"nodes": {
|
|
109
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
110
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
111
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
".ts": {
|
|
115
|
-
"lang": "tsx",
|
|
116
|
-
"name_field": None,
|
|
117
|
-
"nodes": {
|
|
118
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
119
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
120
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
".tsx": {
|
|
124
|
-
"lang": "tsx",
|
|
125
|
-
"name_field": None,
|
|
126
|
-
"nodes": {
|
|
127
|
-
"function_declaration": partial(_f("function {name}()"), style="green"),
|
|
128
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
129
|
-
"export_statement": partial(_f("export {name}"), style="yellow"),
|
|
130
|
-
"interface_declaration": partial(_f("interface {name}"), style="green"),
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
# ───────── systems / compiled ────────────────────────────────────
|
|
134
|
-
".c": {
|
|
135
|
-
"lang": "c",
|
|
136
|
-
"name_field": "declarator", # struct ident is under declarator
|
|
137
|
-
"nodes": {
|
|
138
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
139
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
".h": {
|
|
143
|
-
"lang": "c",
|
|
144
|
-
"name_field": "declarator", # struct ident is under declarator
|
|
145
|
-
"nodes": {
|
|
146
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
147
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
".cpp": {
|
|
151
|
-
"lang": "cpp",
|
|
152
|
-
"name_field": "declarator",
|
|
153
|
-
"nodes": {
|
|
154
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
155
|
-
"class_specifier": partial(_f("class {name}"), style="magenta"),
|
|
156
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
".hpp": {
|
|
160
|
-
"lang": "cpp",
|
|
161
|
-
"name_field": "declarator",
|
|
162
|
-
"nodes": {
|
|
163
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
164
|
-
"class_specifier": partial(_f("class {name}"), style="magenta"),
|
|
165
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
".cc": {
|
|
169
|
-
"lang": "cpp",
|
|
170
|
-
"name_field": "declarator",
|
|
171
|
-
"nodes": {
|
|
172
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
173
|
-
"class_specifier": partial(_f("class {name}"), style="magenta"),
|
|
174
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
".hh": {
|
|
178
|
-
"lang": "cpp",
|
|
179
|
-
"name_field": "declarator",
|
|
180
|
-
"nodes": {
|
|
181
|
-
"function_definition": partial(_f("fn {name}()"), style="green"),
|
|
182
|
-
"class_specifier": partial(_f("class {name}"), style="magenta"),
|
|
183
|
-
"struct_specifier": partial(_f("struct {name}"), style="magenta"),
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
".cs": {
|
|
187
|
-
"lang": "c_sharp",
|
|
188
|
-
"name_field": "name",
|
|
189
|
-
"nodes": {
|
|
190
|
-
"method_declaration": partial(_f("method {name}()"), style="green"),
|
|
191
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
".java": {
|
|
195
|
-
"lang": "java",
|
|
196
|
-
"name_field": "name",
|
|
197
|
-
"nodes": {
|
|
198
|
-
"method_declaration": partial(_f("method {name}()"), style="green"),
|
|
199
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
".kt": {
|
|
203
|
-
"lang": "kotlin",
|
|
204
|
-
"name_field": "name",
|
|
205
|
-
"nodes": {
|
|
206
|
-
"function_declaration": partial(_f("fun {name}()"), style="green"),
|
|
207
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
".swift": {
|
|
211
|
-
"lang": "swift",
|
|
212
|
-
"name_field": "name",
|
|
213
|
-
"nodes": {
|
|
214
|
-
"function_declaration": partial(_f("func {name}()"), style="green"),
|
|
215
|
-
"class_declaration": partial(_f("class {name}"), style="magenta"),
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
".go": {
|
|
219
|
-
"lang": "go",
|
|
220
|
-
"name_field": "name",
|
|
221
|
-
"nodes": {
|
|
222
|
-
"function_declaration": partial(_f("func {name}()"), style="green"),
|
|
223
|
-
"type_spec": partial(_f("type {name}"), style="magenta"),
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
".rs": {
|
|
227
|
-
"lang": "rust",
|
|
228
|
-
"name_field": "name",
|
|
229
|
-
"nodes": {
|
|
230
|
-
"function_item": partial(_f("fn {name}()"), style="green"),
|
|
231
|
-
"struct_item": partial(_f("struct {name}"), style="magenta"),
|
|
232
|
-
"trait_item": partial(_f("trait {name}"), style="magenta"),
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
".zig": {
|
|
236
|
-
"lang": "zig",
|
|
237
|
-
"name_field": "name",
|
|
238
|
-
"nodes": {
|
|
239
|
-
"fn_proto": partial(_f("fn {name}()"), style="green"),
|
|
240
|
-
"struct_decl": partial(_f("struct {name}"), style="magenta"),
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
".scala": {
|
|
244
|
-
"lang": "scala",
|
|
245
|
-
"name_field": "name",
|
|
246
|
-
"nodes": {
|
|
247
|
-
"function_definition": partial(_f("def {name}()"), style="green"),
|
|
248
|
-
"class_definition": partial(_f("class {name}"), style="magenta"),
|
|
249
|
-
"object_definition": partial(_f("object {name}"), style="magenta"),
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
".hs": {
|
|
253
|
-
"lang": "haskell",
|
|
254
|
-
"name_field": "name",
|
|
255
|
-
"nodes": {
|
|
256
|
-
"function_declaration": partial(_f("fun {name}"), style="green"),
|
|
257
|
-
"type_declaration": partial(_f("type {name}"), style="magenta"),
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
".jl": {
|
|
261
|
-
"lang": "julia",
|
|
262
|
-
"name_field": "name",
|
|
263
|
-
"nodes": {
|
|
264
|
-
"function_definition": partial(_f("function {name}()"), style="green"),
|
|
265
|
-
"abstract_type_definition": partial(_f("abstract {name}"), style="magenta"),
|
|
266
|
-
"struct_definition": partial(_f("struct {name}"), style="magenta"),
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
# ──────── markup / style ─────────────────────────────────────────
|
|
270
|
-
".html": {
|
|
271
|
-
"lang": "html",
|
|
272
|
-
"name_field": None,
|
|
273
|
-
"nodes": {
|
|
274
|
-
# rely on parser presence; generic element handling not needed for tests
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
".css": {
|
|
278
|
-
"lang": "css",
|
|
279
|
-
"name_field": None,
|
|
280
|
-
"nodes": {},
|
|
281
|
-
},
|
|
282
|
-
# ───────── scripting (shell / infra) ─────────────────────────────
|
|
283
|
-
".sh": {
|
|
284
|
-
"lang": "bash",
|
|
285
|
-
"name_field": "name",
|
|
286
|
-
"nodes": {"function_definition": partial(_f("fn {name}()"), style="green")},
|
|
287
|
-
},
|
|
288
|
-
".ps1": {
|
|
289
|
-
"lang": "powershell",
|
|
290
|
-
"name_field": "name",
|
|
291
|
-
"nodes": {
|
|
292
|
-
"function_definition": partial(_f("function {name}()"), style="green")
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
# ---------------------------------------------------------------------------
|
|
298
|
-
# Emoji helpers (cute! 🐶)
|
|
299
|
-
# ---------------------------------------------------------------------------
|
|
300
|
-
|
|
301
|
-
_NODE_EMOJIS = {
|
|
302
|
-
"function": "🦴",
|
|
303
|
-
"class": "🏠",
|
|
304
|
-
"struct": "🏗️",
|
|
305
|
-
"interface": "🎛️",
|
|
306
|
-
"trait": "💎",
|
|
307
|
-
"type": "🧩",
|
|
308
|
-
"object": "📦",
|
|
309
|
-
"export": "📤",
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
_FILE_EMOJIS = {
|
|
313
|
-
".py": "🐍",
|
|
314
|
-
".js": "✨",
|
|
315
|
-
".jsx": "✨",
|
|
316
|
-
".ts": "🌀",
|
|
317
|
-
".tsx": "🌀",
|
|
318
|
-
".rb": "💎",
|
|
319
|
-
".go": "🐹",
|
|
320
|
-
".rs": "🦀",
|
|
321
|
-
".java": "☕️",
|
|
322
|
-
".c": "🔧",
|
|
323
|
-
".cpp": "➕",
|
|
324
|
-
".hpp": "➕",
|
|
325
|
-
".swift": "🕊️",
|
|
326
|
-
".kt": "🤖",
|
|
327
|
-
}
|
|
328
|
-
_PARSER_CACHE = {}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def parser_for(lang_name):
|
|
332
|
-
if lang_name not in _PARSER_CACHE:
|
|
333
|
-
_PARSER_CACHE[lang_name] = get_parser(lang_name)
|
|
334
|
-
return _PARSER_CACHE[lang_name]
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
# ----------------------------------------------------------------------
|
|
338
|
-
# helper: breadth-first search for an identifier-ish node
|
|
339
|
-
# ----------------------------------------------------------------------
|
|
340
|
-
def _first_identifier(node):
|
|
341
|
-
from collections import deque
|
|
342
|
-
|
|
343
|
-
q = deque([node])
|
|
344
|
-
while q:
|
|
345
|
-
n = q.popleft()
|
|
346
|
-
if n.type in {"identifier", "property_identifier", "type_identifier"}:
|
|
347
|
-
return n
|
|
348
|
-
q.extend(n.children)
|
|
349
|
-
return None
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def _span(node):
|
|
353
|
-
"""Return "[start:end]" lines (1‑based, inclusive)."""
|
|
354
|
-
start_line = node.start_point[0] + 1
|
|
355
|
-
end_line = node.end_point[0] + 1
|
|
356
|
-
return Text(f" [{start_line}:{end_line}]", style="bold white")
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def _emoji_for_node_type(ts_type: str) -> str:
|
|
360
|
-
"""Return a cute emoji for a given Tree-sitter node type (best-effort)."""
|
|
361
|
-
# naive mapping based on substrings – keeps it simple
|
|
362
|
-
if "function" in ts_type or "method" in ts_type or ts_type.startswith("fn_"):
|
|
363
|
-
return _NODE_EMOJIS["function"]
|
|
364
|
-
if "class" in ts_type:
|
|
365
|
-
return _NODE_EMOJIS["class"]
|
|
366
|
-
if "struct" in ts_type:
|
|
367
|
-
return _NODE_EMOJIS["struct"]
|
|
368
|
-
if "interface" in ts_type:
|
|
369
|
-
return _NODE_EMOJIS["interface"]
|
|
370
|
-
if "trait" in ts_type:
|
|
371
|
-
return _NODE_EMOJIS["trait"]
|
|
372
|
-
if "type_spec" in ts_type or "type_declaration" in ts_type:
|
|
373
|
-
return _NODE_EMOJIS["type"]
|
|
374
|
-
if "object" in ts_type:
|
|
375
|
-
return _NODE_EMOJIS["object"]
|
|
376
|
-
if ts_type.startswith("export"):
|
|
377
|
-
return _NODE_EMOJIS["export"]
|
|
378
|
-
return ""
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
# ----------------------------------------------------------------------
|
|
382
|
-
# traversal (clean)
|
|
383
|
-
# ----------------------------------------------------------------------
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def _walk_fix(ts_node, rich_parent, info):
|
|
387
|
-
"""Recursive traversal adding child nodes with emoji labels."""
|
|
388
|
-
nodes_cfg = info["nodes"]
|
|
389
|
-
name_field = info["name_field"]
|
|
390
|
-
|
|
391
|
-
for child in ts_node.children:
|
|
392
|
-
n_type = child.type
|
|
393
|
-
if n_type in nodes_cfg:
|
|
394
|
-
style = nodes_cfg[n_type].keywords["style"]
|
|
395
|
-
ident = (
|
|
396
|
-
child.child_by_field_name(name_field)
|
|
397
|
-
if name_field
|
|
398
|
-
else _first_identifier(child)
|
|
399
|
-
)
|
|
400
|
-
label_text = ident.text.decode() if ident else "<anon>"
|
|
401
|
-
label = nodes_cfg[n_type].func(label_text)
|
|
402
|
-
emoji = _emoji_for_node_type(n_type)
|
|
403
|
-
if emoji:
|
|
404
|
-
label = f"{emoji} {label}"
|
|
405
|
-
branch = rich_parent.add(Text(label, style=style) + _span(child))
|
|
406
|
-
_walk_fix(child, branch, info)
|
|
407
|
-
else:
|
|
408
|
-
_walk_fix(child, rich_parent, info)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
# ----------------------------------------------------------------------
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def _walk(ts_node, rich_parent, info):
|
|
415
|
-
nodes_cfg = info["nodes"]
|
|
416
|
-
name_field = info["name_field"]
|
|
417
|
-
|
|
418
|
-
for child in ts_node.children:
|
|
419
|
-
t = child.type
|
|
420
|
-
if t in nodes_cfg:
|
|
421
|
-
style = nodes_cfg[t].keywords["style"]
|
|
422
|
-
|
|
423
|
-
if name_field:
|
|
424
|
-
ident = child.child_by_field_name(name_field)
|
|
425
|
-
else:
|
|
426
|
-
ident = _first_identifier(child)
|
|
427
|
-
|
|
428
|
-
label_text = ident.text.decode() if ident else "<anon>"
|
|
429
|
-
label = nodes_cfg[t].func(label_text)
|
|
430
|
-
emoji = _emoji_for_node_type(t)
|
|
431
|
-
if emoji:
|
|
432
|
-
label = f"{emoji} {label}"
|
|
433
|
-
branch = rich_parent.add(Text(label, style=style) + _span(child))
|
|
434
|
-
_walk(child, branch, info)
|
|
435
|
-
else:
|
|
436
|
-
_walk(child, rich_parent, info)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def map_code_file(filepath):
|
|
440
|
-
ext = Path(filepath).suffix
|
|
441
|
-
info = LANGS.get(ext)
|
|
442
|
-
if not info:
|
|
443
|
-
return None
|
|
444
|
-
|
|
445
|
-
code = Path(filepath).read_bytes()
|
|
446
|
-
parser = parser_for(info["lang"])
|
|
447
|
-
tree = parser.parse(code)
|
|
448
|
-
|
|
449
|
-
file_emoji = _FILE_EMOJIS.get(ext, "📄")
|
|
450
|
-
root_label = f"{file_emoji} {Path(filepath).name}"
|
|
451
|
-
base = RichTree(Text(root_label, style="bold cyan"))
|
|
452
|
-
|
|
453
|
-
if tree.root_node.has_error:
|
|
454
|
-
base.add(Text("⚠️ syntax error", style="bold red"))
|
|
455
|
-
|
|
456
|
-
_walk_fix(tree.root_node, base, info)
|
|
457
|
-
return base
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
def make_code_map(directory: str, ignore_tests: bool = True) -> str:
|
|
461
|
-
"""Generate a Rich-rendered code map including directory hierarchy.
|
|
462
|
-
|
|
463
|
-
Args:
|
|
464
|
-
directory: Root directory to scan.
|
|
465
|
-
ignore_tests: Whether to skip files with 'test' in the name.
|
|
466
|
-
|
|
467
|
-
Returns:
|
|
468
|
-
Plain-text rendering of the generated Rich tree (last 1k chars).
|
|
469
|
-
"""
|
|
470
|
-
# Create root of tree representing starting directory
|
|
471
|
-
base_tree = RichTree(Text(Path(directory).name, style="bold magenta"))
|
|
472
|
-
|
|
473
|
-
# Cache to ensure we reuse RichTree nodes per directory path
|
|
474
|
-
dir_nodes: dict[str, RichTree] = {
|
|
475
|
-
Path(directory).resolve(): base_tree
|
|
476
|
-
} # key=abs path
|
|
477
|
-
|
|
478
|
-
for root, dirs, files in os.walk(directory):
|
|
479
|
-
# ignore dot-folders early
|
|
480
|
-
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
|
481
|
-
|
|
482
|
-
abs_root = Path(root).resolve()
|
|
483
|
-
|
|
484
|
-
# Ensure current directory has a node; create if coming from parent
|
|
485
|
-
if abs_root not in dir_nodes and abs_root != Path(directory).resolve():
|
|
486
|
-
rel_parts = abs_root.relative_to(directory).parts
|
|
487
|
-
parent_path = Path(directory).resolve()
|
|
488
|
-
for part in rel_parts: # walk down creating nodes as needed
|
|
489
|
-
parent_node = dir_nodes[parent_path]
|
|
490
|
-
current_path = parent_path / part
|
|
491
|
-
if current_path not in dir_nodes:
|
|
492
|
-
dir_label = Text(part, style="bold magenta")
|
|
493
|
-
dir_node = parent_node.add(dir_label)
|
|
494
|
-
dir_nodes[current_path] = dir_node
|
|
495
|
-
parent_path = current_path
|
|
496
|
-
|
|
497
|
-
current_node = dir_nodes.get(abs_root, base_tree)
|
|
498
|
-
|
|
499
|
-
for f in files:
|
|
500
|
-
file_path = os.path.join(root, f)
|
|
501
|
-
if should_ignore_path(file_path):
|
|
502
|
-
continue
|
|
503
|
-
if ignore_tests and "test" in f:
|
|
504
|
-
continue
|
|
505
|
-
try:
|
|
506
|
-
file_tree = map_code_file(file_path)
|
|
507
|
-
if file_tree is not None:
|
|
508
|
-
current_node.add(file_tree)
|
|
509
|
-
except Exception:
|
|
510
|
-
current_node.add(Text(f"[error reading {f}]", style="bold red"))
|
|
511
|
-
|
|
512
|
-
# Render and return last 1000 characters
|
|
513
|
-
buf = Console(record=True, width=120)
|
|
514
|
-
buf.print(base_tree)
|
|
515
|
-
return buf.export_text()[-1000:]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|