code-puppy 0.0.60__tar.gz → 0.0.61__tar.gz

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 (28) hide show
  1. {code_puppy-0.0.60 → code_puppy-0.0.61}/PKG-INFO +3 -1
  2. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/agent_prompts.py +1 -0
  3. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/meta_command_handler.py +2 -3
  4. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/common.py +51 -0
  5. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/file_operations.py +19 -45
  6. code_puppy-0.0.61/code_puppy/tools/ts_code_map.py +393 -0
  7. {code_puppy-0.0.60 → code_puppy-0.0.61}/pyproject.toml +3 -1
  8. code_puppy-0.0.60/code_puppy/tools/code_map.py +0 -92
  9. {code_puppy-0.0.60 → code_puppy-0.0.61}/.gitignore +0 -0
  10. {code_puppy-0.0.60 → code_puppy-0.0.61}/LICENSE +0 -0
  11. {code_puppy-0.0.60 → code_puppy-0.0.61}/README.md +0 -0
  12. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/__init__.py +0 -0
  13. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/agent.py +0 -0
  14. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/__init__.py +0 -0
  15. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/file_path_completion.py +0 -0
  16. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/model_picker_completion.py +0 -0
  17. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  18. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/command_line/utils.py +0 -0
  19. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/config.py +0 -0
  20. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/main.py +0 -0
  21. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/model_factory.py +0 -0
  22. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/models.json +0 -0
  23. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/session_memory.py +0 -0
  24. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/__init__.py +0 -0
  25. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/command_runner.py +0 -0
  26. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/file_modifications.py +0 -0
  27. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/tools/web_search.py +0 -0
  28. {code_puppy-0.0.60 → code_puppy-0.0.61}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.60
3
+ Version: 0.0.61
4
4
  Summary: Code generation agent
5
5
  Author: Michael Pfaffenberger
6
6
  License: MIT
@@ -27,6 +27,8 @@ Requires-Dist: python-dotenv>=1.0.0
27
27
  Requires-Dist: rapidfuzz>=3.13.0
28
28
  Requires-Dist: rich>=13.4.2
29
29
  Requires-Dist: ruff>=0.11.11
30
+ Requires-Dist: tree-sitter-language-pack>=0.8.0
31
+ Requires-Dist: tree-sitter-typescript>=0.23.2
30
32
  Description-Content-Type: text/markdown
31
33
 
32
34
  # 🐶 Code Puppy 🐶
@@ -30,6 +30,7 @@ File Operations:
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.
33
+ - code_map(directory="."): Use this to generate a code map for the specified directory.
33
34
 
34
35
  Tool Usage Instructions:
35
36
 
@@ -29,7 +29,7 @@ def handle_meta_command(command: str, console: Console) -> bool:
29
29
 
30
30
  # ~codemap (code structure visualization)
31
31
  if command.startswith("~codemap"):
32
- from code_puppy.tools.code_map import make_code_map
32
+ from code_puppy.tools.ts_code_map import make_code_map
33
33
 
34
34
  tokens = command.split()
35
35
  if len(tokens) > 1:
@@ -37,8 +37,7 @@ def handle_meta_command(command: str, console: Console) -> bool:
37
37
  else:
38
38
  target_dir = os.getcwd()
39
39
  try:
40
- tree = make_code_map(target_dir, show_doc=True)
41
- console.print(tree)
40
+ make_code_map(target_dir, ignore_tests=True)
42
41
  except Exception as e:
43
42
  console.print(f"[red]Error generating code map:[/red] {e}")
44
43
  return True
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import fnmatch
2
3
 
3
4
  from typing import Optional, Tuple
4
5
 
@@ -8,6 +9,56 @@ from rich.console import Console
8
9
  NO_COLOR = bool(int(os.environ.get("CODE_PUPPY_NO_COLOR", "0")))
9
10
  console = Console(no_color=NO_COLOR)
10
11
 
12
+
13
+ # -------------------
14
+ # Shared ignore patterns/helpers
15
+ # -------------------
16
+ IGNORE_PATTERNS = [
17
+ "**/node_modules/**",
18
+ "**/node_modules/**/*.js",
19
+ "node_modules/**",
20
+ "node_modules",
21
+ "**/.git/**",
22
+ "**/.git",
23
+ ".git/**",
24
+ ".git",
25
+ "**/__pycache__/**",
26
+ "**/__pycache__",
27
+ "__pycache__/**",
28
+ "__pycache__",
29
+ "**/.DS_Store",
30
+ ".DS_Store",
31
+ "**/.env",
32
+ ".env",
33
+ "**/.venv/**",
34
+ "**/.venv",
35
+ "**/venv/**",
36
+ "**/venv",
37
+ "**/.idea/**",
38
+ "**/.idea",
39
+ "**/.vscode/**",
40
+ "**/.vscode",
41
+ "**/dist/**",
42
+ "**/dist",
43
+ "**/build/**",
44
+ "**/build",
45
+ "**/*.pyc",
46
+ "**/*.pyo",
47
+ "**/*.pyd",
48
+ "**/*.so",
49
+ "**/*.dll",
50
+ "**/.*",
51
+ ]
52
+
53
+
54
+ def should_ignore_path(path: str) -> bool:
55
+ """Return True if *path* matches any pattern in IGNORE_PATTERNS."""
56
+ for pattern in IGNORE_PATTERNS:
57
+ if fnmatch.fnmatch(path, pattern):
58
+ return True
59
+ return False
60
+
61
+
11
62
  JW_THRESHOLD = 0.95
12
63
 
13
64
 
@@ -1,5 +1,5 @@
1
1
  # file_operations.py
2
- import fnmatch
2
+
3
3
  import os
4
4
  from typing import Any, Dict, List
5
5
 
@@ -10,50 +10,7 @@ from code_puppy.tools.common import console
10
10
  # ---------------------------------------------------------------------------
11
11
  # Module-level helper functions (exposed for unit tests _and_ used as tools)
12
12
  # ---------------------------------------------------------------------------
13
- IGNORE_PATTERNS = [
14
- "**/node_modules/**",
15
- "**/node_modules/**/*.js",
16
- "node_modules/**",
17
- "node_modules",
18
- "**/.git/**",
19
- "**/.git",
20
- ".git/**",
21
- ".git",
22
- "**/__pycache__/**",
23
- "**/__pycache__",
24
- "__pycache__/**",
25
- "__pycache__",
26
- "**/.DS_Store",
27
- ".DS_Store",
28
- "**/.env",
29
- ".env",
30
- "**/.venv/**",
31
- "**/.venv",
32
- "**/venv/**",
33
- "**/venv",
34
- "**/.idea/**",
35
- "**/.idea",
36
- "**/.vscode/**",
37
- "**/.vscode",
38
- "**/dist/**",
39
- "**/dist",
40
- "**/build/**",
41
- "**/build",
42
- "**/*.pyc",
43
- "**/*.pyo",
44
- "**/*.pyd",
45
- "**/*.so",
46
- "**/*.dll",
47
- "**/*.exe",
48
- ]
49
-
50
-
51
- def should_ignore_path(path: str) -> bool:
52
- """Return True if *path* matches any pattern in IGNORE_PATTERNS."""
53
- for pattern in IGNORE_PATTERNS:
54
- if fnmatch.fnmatch(path, pattern):
55
- return True
56
- return False
13
+ from code_puppy.tools.common import should_ignore_path
57
14
 
58
15
 
59
16
  def _list_files(
@@ -323,3 +280,20 @@ def register_file_operations_tools(agent):
323
280
  context: RunContext, search_string: str, directory: str = "."
324
281
  ) -> List[Dict[str, Any]]:
325
282
  return _grep(context, search_string, directory)
283
+
284
+ @agent.tool
285
+ def code_map(context: RunContext, directory: str = ".") -> str:
286
+ """Generate a code map for the specified directory.
287
+ This will have a list of all function / class names and nested structure
288
+ Args:
289
+ context: The context object.
290
+ directory: The directory to generate the code map for.
291
+
292
+ Returns:
293
+ A string containing the code map.
294
+ """
295
+ console.print("[bold white on blue] CODE MAP [/bold white on blue]")
296
+ from code_puppy.tools.ts_code_map import make_code_map
297
+
298
+ result = make_code_map(directory, ignore_tests=True)
299
+ return result
@@ -0,0 +1,393 @@
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
+ # ───────── scripting (shell / infra) ─────────────────────────────
270
+ ".sh": {
271
+ "lang": "bash",
272
+ "name_field": "name",
273
+ "nodes": {"function_definition": partial(_f("fn {name}()"), style="green")},
274
+ },
275
+ ".ps1": {
276
+ "lang": "powershell",
277
+ "name_field": "name",
278
+ "nodes": {
279
+ "function_definition": partial(_f("function {name}()"), style="green")
280
+ },
281
+ },
282
+ # ───────── web / data description (pure-function view) ───────────
283
+ ".html": {
284
+ "lang": "html",
285
+ "name_field": "name", # rarely useful, but included for completeness
286
+ "nodes": {"element": partial(_f("<{name}>"), style="yellow")},
287
+ },
288
+ ".css": {
289
+ "lang": "css",
290
+ "name_field": "name",
291
+ "nodes": {
292
+ "class_selector": partial(_f(".{name}"), style="yellow"),
293
+ "id_selector": partial(_f("#{name}"), style="yellow"),
294
+ },
295
+ },
296
+ }
297
+
298
+ # Cache parsers so we don’t re-create them file-after-file
299
+ _PARSER_CACHE = {}
300
+
301
+
302
+ def parser_for(lang_name):
303
+ if lang_name not in _PARSER_CACHE:
304
+ _PARSER_CACHE[lang_name] = get_parser(lang_name)
305
+ return _PARSER_CACHE[lang_name]
306
+
307
+
308
+ # ----------------------------------------------------------------------
309
+ # helper: breadth-first search for an identifier-ish node
310
+ # ----------------------------------------------------------------------
311
+ def _first_identifier(node):
312
+ from collections import deque
313
+
314
+ q = deque([node])
315
+ while q:
316
+ n = q.popleft()
317
+ if n.type in {"identifier", "property_identifier", "type_identifier"}:
318
+ return n
319
+ q.extend(n.children)
320
+ return None
321
+
322
+
323
+ def _span(node):
324
+ """Return "[start:end]" lines (1‑based, inclusive)."""
325
+ start_line = node.start_point[0] + 1
326
+ end_line = node.end_point[0] + 1
327
+ return Text(f" [{start_line}:{end_line}]", style="bold white")
328
+
329
+
330
+ def _walk(ts_node, rich_parent, info):
331
+ nodes_cfg = info["nodes"]
332
+ name_field = info["name_field"]
333
+
334
+ for child in ts_node.children:
335
+ t = child.type
336
+ if t in nodes_cfg:
337
+ style = nodes_cfg[t].keywords["style"]
338
+
339
+ if name_field:
340
+ ident = child.child_by_field_name(name_field)
341
+ else:
342
+ ident = _first_identifier(child)
343
+
344
+ label_text = ident.text.decode() if ident else "<anon>"
345
+ label = nodes_cfg[t].func(label_text)
346
+ branch = rich_parent.add(Text(label, style=style) + _span(child))
347
+ _walk(child, branch, info)
348
+ else:
349
+ _walk(child, rich_parent, info)
350
+
351
+
352
+ def map_code_file(filepath):
353
+ ext = Path(filepath).suffix
354
+ info = LANGS.get(ext)
355
+ if not info:
356
+ return None
357
+
358
+ code = Path(filepath).read_bytes()
359
+ parser = parser_for(info["lang"])
360
+ tree = parser.parse(code)
361
+
362
+ root_label = Path(filepath).name
363
+ base = RichTree(Text(root_label, style="bold cyan"))
364
+
365
+ if tree.root_node.has_error:
366
+ base.add(Text("⚠️ syntax error", style="bold red"))
367
+
368
+ _walk(tree.root_node, base, info)
369
+ return base
370
+
371
+
372
+ def make_code_map(directory: str, ignore_tests: bool = True) -> str:
373
+ base_tree = RichTree(Text(Path(directory).name, style="bold magenta"))
374
+
375
+ for root, dirs, files in os.walk(directory):
376
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
377
+ for f in files:
378
+ if (
379
+ should_ignore_path(os.path.join(root, f))
380
+ or ignore_tests
381
+ and "test" in f
382
+ ):
383
+ continue
384
+ try:
385
+ file_tree = map_code_file(os.path.join(root, f))
386
+ if file_tree is not None:
387
+ base_tree.add(file_tree)
388
+ except Exception:
389
+ base_tree.add(Text(f"[error reading {f}]", style="bold red"))
390
+
391
+ buf = Console(record=True, width=120)
392
+ buf.print(base_tree)
393
+ return buf.export_text()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.60"
7
+ version = "0.0.61"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -23,6 +23,8 @@ dependencies = [
23
23
  "pathspec>=0.11.0",
24
24
  "rapidfuzz>=3.13.0",
25
25
  "json-repair>=0.46.2",
26
+ "tree-sitter-language-pack>=0.8.0",
27
+ "tree-sitter-typescript>=0.23.2",
26
28
  ]
27
29
  dev-dependencies = [
28
30
  "pytest>=8.3.4",
@@ -1,92 +0,0 @@
1
- import ast
2
- import os
3
-
4
- import pathspec
5
- from rich.text import Text
6
- from rich.tree import Tree
7
-
8
-
9
- def summarize_node(node: ast.AST) -> str:
10
- if isinstance(node, ast.ClassDef):
11
- return f"class {node.name}"
12
- if isinstance(node, ast.FunctionDef):
13
- return f"def {node.name}()"
14
- return ""
15
-
16
-
17
- def get_docstring(node: ast.AST) -> str:
18
- doc = ast.get_docstring(node)
19
- if doc:
20
- lines = doc.strip().split("\n")
21
- return lines[0] if lines else doc.strip()
22
- return ""
23
-
24
-
25
- def map_python_file(file_path: str, show_doc: bool = True) -> Tree:
26
- tree = Tree(Text(file_path, style="bold cyan"))
27
- with open(file_path, "r", encoding="utf-8") as f:
28
- root = ast.parse(f.read(), filename=file_path)
29
- for node in root.body:
30
- summary = summarize_node(node)
31
- if summary:
32
- t = Tree(summary)
33
- if show_doc:
34
- doc = get_docstring(node)
35
- if doc:
36
- t.add(Text(f'"{doc}"', style="dim"))
37
- # Add inner functions
38
- if hasattr(node, "body"):
39
- for subnode in getattr(node, "body"):
40
- subsum = summarize_node(subnode)
41
- if subsum:
42
- sub_t = Tree(subsum)
43
- doc2 = get_docstring(subnode)
44
- if doc2:
45
- sub_t.add(Text(f'"{doc2}"', style="dim"))
46
- t.add(sub_t)
47
- tree.add(t)
48
- return tree
49
-
50
-
51
- def load_gitignore(directory: str):
52
- gitignore_file = os.path.join(directory, ".gitignore")
53
- if os.path.exists(gitignore_file):
54
- with open(gitignore_file, "r") as f:
55
- spec = pathspec.PathSpec.from_lines("gitwildmatch", f)
56
- return spec
57
- else:
58
- return pathspec.PathSpec.from_lines("gitwildmatch", [])
59
-
60
-
61
- def make_code_map(directory: str, show_doc: bool = True) -> Tree:
62
- """
63
- Recursively build a Tree displaying the code structure of all .py files in a directory,
64
- ignoring files listed in .gitignore if present.
65
- """
66
- base_tree = Tree(Text(directory, style="bold magenta"))
67
-
68
- spec = load_gitignore(directory)
69
- abs_directory = os.path.abspath(directory)
70
-
71
- for root, dirs, files in os.walk(directory):
72
- rel_root = os.path.relpath(root, abs_directory)
73
- # Remove ignored directories in-place for os.walk to not descend
74
- dirs[:] = [
75
- d
76
- for d in dirs
77
- if not spec.match_file(os.path.normpath(os.path.join(rel_root, d)))
78
- ]
79
- for fname in files:
80
- rel_file = os.path.normpath(os.path.join(rel_root, fname))
81
- if fname.endswith(".py") and not fname.startswith("__"):
82
- if not spec.match_file(rel_file):
83
- fpath = os.path.join(root, fname)
84
- try:
85
- file_tree = map_python_file(fpath, show_doc=show_doc)
86
- base_tree.add(file_tree)
87
- except Exception as e:
88
- err = Tree(
89
- Text(f"[error reading {fname}: {e}]", style="bold red")
90
- )
91
- base_tree.add(err)
92
- return base_tree
File without changes
File without changes
File without changes