kopipasta 0.28.0__py3-none-any.whl → 0.29.0__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.
Potentially problematic release.
This version of kopipasta might be problematic. Click here for more details.
- kopipasta/file.py +47 -0
- kopipasta/main.py +375 -288
- kopipasta/prompt.py +163 -0
- kopipasta/tree_selector.py +510 -0
- {kopipasta-0.28.0.dist-info → kopipasta-0.29.0.dist-info}/METADATA +3 -1
- kopipasta-0.29.0.dist-info/RECORD +12 -0
- kopipasta-0.28.0.dist-info/RECORD +0 -9
- {kopipasta-0.28.0.dist-info → kopipasta-0.29.0.dist-info}/LICENSE +0 -0
- {kopipasta-0.28.0.dist-info → kopipasta-0.29.0.dist-info}/WHEEL +0 -0
- {kopipasta-0.28.0.dist-info → kopipasta-0.29.0.dist-info}/entry_points.txt +0 -0
- {kopipasta-0.28.0.dist-info → kopipasta-0.29.0.dist-info}/top_level.txt +0 -0
kopipasta/main.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
import csv
|
|
3
|
-
import io
|
|
4
2
|
import os
|
|
5
3
|
import argparse
|
|
6
4
|
import re
|
|
7
5
|
import subprocess
|
|
8
6
|
import tempfile
|
|
7
|
+
import shutil
|
|
9
8
|
from typing import Dict, List, Optional, Set, Tuple
|
|
10
9
|
import pyperclip
|
|
11
|
-
import fnmatch
|
|
12
10
|
from pygments import highlight
|
|
13
11
|
from pygments.lexers import get_lexer_for_filename, TextLexer
|
|
14
12
|
from pygments.formatters import TerminalFormatter
|
|
@@ -16,9 +14,10 @@ import pygments.util
|
|
|
16
14
|
|
|
17
15
|
import requests
|
|
18
16
|
|
|
17
|
+
from kopipasta.file import FileTuple, get_human_readable_size, is_binary, is_ignored, is_large_file, read_file_contents
|
|
19
18
|
import kopipasta.import_parser as import_parser
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
from kopipasta.tree_selector import TreeSelector
|
|
20
|
+
from kopipasta.prompt import generate_prompt_template, get_file_snippet, get_language_for_file
|
|
22
21
|
|
|
23
22
|
def _propose_and_add_dependencies(
|
|
24
23
|
file_just_added: str,
|
|
@@ -33,7 +32,7 @@ def _propose_and_add_dependencies(
|
|
|
33
32
|
if language not in ['python', 'typescript', 'javascript', 'tsx', 'jsx']:
|
|
34
33
|
return [], 0 # Only analyze languages we can parse
|
|
35
34
|
|
|
36
|
-
print(f"Analyzing {
|
|
35
|
+
print(f"Analyzing {os.path.relpath(file_just_added)} for local dependencies...")
|
|
37
36
|
|
|
38
37
|
try:
|
|
39
38
|
file_content = read_file_contents(file_just_added)
|
|
@@ -59,7 +58,7 @@ def _propose_and_add_dependencies(
|
|
|
59
58
|
|
|
60
59
|
print(f"\nFound {len(suggested_deps)} new local {'dependency' if len(suggested_deps) == 1 else 'dependencies'}:")
|
|
61
60
|
for i, dep_path in enumerate(suggested_deps):
|
|
62
|
-
print(f" ({i+1}) {
|
|
61
|
+
print(f" ({i+1}) {os.path.relpath(dep_path)}")
|
|
63
62
|
|
|
64
63
|
while True:
|
|
65
64
|
choice = input("\nAdd dependencies? (a)ll, (n)one, or enter numbers (e.g. 1, 3-4): ").lower()
|
|
@@ -115,12 +114,12 @@ def _propose_and_add_dependencies(
|
|
|
115
114
|
file_size = os.path.getsize(dep_path)
|
|
116
115
|
newly_added_files.append((dep_path, False, None, get_language_for_file(dep_path)))
|
|
117
116
|
char_count_delta += file_size
|
|
118
|
-
print(f"Added dependency: {
|
|
117
|
+
print(f"Added dependency: {os.path.relpath(dep_path)} ({get_human_readable_size(file_size)})")
|
|
119
118
|
|
|
120
119
|
return newly_added_files, char_count_delta
|
|
121
120
|
|
|
122
121
|
except Exception as e:
|
|
123
|
-
print(f"Warning: Could not analyze dependencies for {
|
|
122
|
+
print(f"Warning: Could not analyze dependencies for {os.path.relpath(file_just_added)}: {e}")
|
|
124
123
|
return [], 0
|
|
125
124
|
|
|
126
125
|
def get_colored_code(file_path, code):
|
|
@@ -155,85 +154,6 @@ def read_gitignore():
|
|
|
155
154
|
gitignore_patterns.append(line)
|
|
156
155
|
return gitignore_patterns
|
|
157
156
|
|
|
158
|
-
def is_ignored(path, ignore_patterns):
|
|
159
|
-
path = os.path.normpath(path)
|
|
160
|
-
for pattern in ignore_patterns:
|
|
161
|
-
if fnmatch.fnmatch(os.path.basename(path), pattern) or fnmatch.fnmatch(path, pattern):
|
|
162
|
-
return True
|
|
163
|
-
return False
|
|
164
|
-
|
|
165
|
-
def is_binary(file_path):
|
|
166
|
-
try:
|
|
167
|
-
with open(file_path, 'rb') as file:
|
|
168
|
-
chunk = file.read(1024)
|
|
169
|
-
if b'\0' in chunk: # null bytes indicate binary file
|
|
170
|
-
return True
|
|
171
|
-
if file_path.lower().endswith(('.json', '.csv')):
|
|
172
|
-
return False
|
|
173
|
-
return False
|
|
174
|
-
except IOError:
|
|
175
|
-
return False
|
|
176
|
-
|
|
177
|
-
def get_human_readable_size(size):
|
|
178
|
-
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
|
179
|
-
if size < 1024.0:
|
|
180
|
-
return f"{size:.2f} {unit}"
|
|
181
|
-
size /= 1024.0
|
|
182
|
-
|
|
183
|
-
def is_large_file(file_path, threshold=102400): # 100 KB threshold
|
|
184
|
-
return os.path.getsize(file_path) > threshold
|
|
185
|
-
|
|
186
|
-
def get_project_structure(ignore_patterns):
|
|
187
|
-
tree = []
|
|
188
|
-
for root, dirs, files in os.walk('.'):
|
|
189
|
-
dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d), ignore_patterns)]
|
|
190
|
-
files = [f for f in files if not is_ignored(os.path.join(root, f), ignore_patterns)]
|
|
191
|
-
level = root.replace('.', '').count(os.sep)
|
|
192
|
-
indent = ' ' * 4 * level + '|-- '
|
|
193
|
-
tree.append(f"{indent}{os.path.basename(root)}/")
|
|
194
|
-
subindent = ' ' * 4 * (level + 1) + '|-- '
|
|
195
|
-
for f in files:
|
|
196
|
-
tree.append(f"{subindent}{f}")
|
|
197
|
-
return '\n'.join(tree)
|
|
198
|
-
|
|
199
|
-
def read_file_contents(file_path):
|
|
200
|
-
try:
|
|
201
|
-
with open(file_path, 'r') as file:
|
|
202
|
-
return file.read()
|
|
203
|
-
except Exception as e:
|
|
204
|
-
print(f"Error reading {file_path}: {e}")
|
|
205
|
-
return ""
|
|
206
|
-
|
|
207
|
-
def get_relative_path(file_path):
|
|
208
|
-
return os.path.relpath(file_path)
|
|
209
|
-
|
|
210
|
-
def get_language_for_file(file_path):
|
|
211
|
-
extension = os.path.splitext(file_path)[1].lower()
|
|
212
|
-
language_map = {
|
|
213
|
-
'.py': 'python',
|
|
214
|
-
'.js': 'javascript',
|
|
215
|
-
'.jsx': 'jsx',
|
|
216
|
-
'.ts': 'typescript',
|
|
217
|
-
'.tsx': 'tsx',
|
|
218
|
-
'.html': 'html',
|
|
219
|
-
'.htm': 'html',
|
|
220
|
-
'.css': 'css',
|
|
221
|
-
'.json': 'json',
|
|
222
|
-
'.md': 'markdown',
|
|
223
|
-
'.sql': 'sql',
|
|
224
|
-
'.sh': 'bash',
|
|
225
|
-
'.yml': 'yaml',
|
|
226
|
-
'.yaml': 'yaml',
|
|
227
|
-
'.go': 'go',
|
|
228
|
-
'.toml': 'toml',
|
|
229
|
-
'.c': 'c',
|
|
230
|
-
'.cpp': 'cpp',
|
|
231
|
-
'.cc': 'cpp',
|
|
232
|
-
'.h': 'cpp',
|
|
233
|
-
'.hpp': 'cpp',
|
|
234
|
-
}
|
|
235
|
-
return language_map.get(extension, '')
|
|
236
|
-
|
|
237
157
|
def split_python_file(file_content):
|
|
238
158
|
"""
|
|
239
159
|
Splits Python code into logical chunks using the AST module.
|
|
@@ -529,17 +449,6 @@ def get_placeholder_comment(language):
|
|
|
529
449
|
}
|
|
530
450
|
return comments.get(language, comments['default'])
|
|
531
451
|
|
|
532
|
-
def get_file_snippet(file_path, max_lines=50, max_bytes=4096):
|
|
533
|
-
snippet = ""
|
|
534
|
-
byte_count = 0
|
|
535
|
-
with open(file_path, 'r') as file:
|
|
536
|
-
for i, line in enumerate(file):
|
|
537
|
-
if i >= max_lines or byte_count >= max_bytes:
|
|
538
|
-
break
|
|
539
|
-
snippet += line
|
|
540
|
-
byte_count += len(line.encode('utf-8'))
|
|
541
|
-
return snippet
|
|
542
|
-
|
|
543
452
|
def get_colored_file_snippet(file_path, max_lines=50, max_bytes=4096):
|
|
544
453
|
snippet = get_file_snippet(file_path, max_lines, max_bytes)
|
|
545
454
|
return get_colored_code(file_path, snippet)
|
|
@@ -548,7 +457,211 @@ def print_char_count(count):
|
|
|
548
457
|
token_estimate = count // 4
|
|
549
458
|
print(f"\rCurrent prompt size: {count} characters (~ {token_estimate} tokens)", flush=True)
|
|
550
459
|
|
|
551
|
-
def
|
|
460
|
+
def grep_files_in_directory(pattern: str, directory: str, ignore_patterns: List[str]) -> List[Tuple[str, List[str], int]]:
|
|
461
|
+
"""
|
|
462
|
+
Search for files containing a pattern using ag (silver searcher).
|
|
463
|
+
Returns list of (filepath, preview_lines, match_count).
|
|
464
|
+
"""
|
|
465
|
+
# Check if ag is available
|
|
466
|
+
if not shutil.which('ag'):
|
|
467
|
+
print("Silver Searcher (ag) not found. Install it for grep functionality:")
|
|
468
|
+
print(" - Mac: brew install the_silver_searcher")
|
|
469
|
+
print(" - Ubuntu/Debian: apt-get install silversearcher-ag")
|
|
470
|
+
print(" - Other: https://github.com/ggreer/the_silver_searcher")
|
|
471
|
+
return []
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
# First get files with matches
|
|
475
|
+
cmd = [
|
|
476
|
+
'ag',
|
|
477
|
+
'--files-with-matches',
|
|
478
|
+
'--nocolor',
|
|
479
|
+
'--ignore-case',
|
|
480
|
+
pattern,
|
|
481
|
+
directory
|
|
482
|
+
]
|
|
483
|
+
|
|
484
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
485
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
486
|
+
return []
|
|
487
|
+
|
|
488
|
+
files = result.stdout.strip().split('\n')
|
|
489
|
+
grep_results = []
|
|
490
|
+
|
|
491
|
+
for file in files:
|
|
492
|
+
if is_ignored(file, ignore_patterns) or is_binary(file):
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
# Get match count and preview lines
|
|
496
|
+
count_cmd = ['ag', '--count', '--nocolor', pattern, file]
|
|
497
|
+
count_result = subprocess.run(count_cmd, capture_output=True, text=True)
|
|
498
|
+
match_count = 0
|
|
499
|
+
if count_result.stdout:
|
|
500
|
+
# ag --count outputs "filename:count"
|
|
501
|
+
# We need to handle filenames that might contain colons
|
|
502
|
+
stdout_line = count_result.stdout.strip()
|
|
503
|
+
# Find the last colon to separate filename from count
|
|
504
|
+
last_colon_idx = stdout_line.rfind(':')
|
|
505
|
+
if last_colon_idx > 0:
|
|
506
|
+
try:
|
|
507
|
+
match_count = int(stdout_line[last_colon_idx + 1:])
|
|
508
|
+
except ValueError:
|
|
509
|
+
match_count = 1
|
|
510
|
+
else:
|
|
511
|
+
match_count = 1
|
|
512
|
+
|
|
513
|
+
# Get preview of matches (up to 3 lines)
|
|
514
|
+
preview_cmd = [
|
|
515
|
+
'ag',
|
|
516
|
+
'--max-count=3',
|
|
517
|
+
'--nocolor',
|
|
518
|
+
'--noheading',
|
|
519
|
+
'--numbers',
|
|
520
|
+
pattern,
|
|
521
|
+
file
|
|
522
|
+
]
|
|
523
|
+
preview_result = subprocess.run(preview_cmd, capture_output=True, text=True)
|
|
524
|
+
preview_lines = []
|
|
525
|
+
if preview_result.stdout:
|
|
526
|
+
for line in preview_result.stdout.strip().split('\n')[:3]:
|
|
527
|
+
# Format: "line_num:content"
|
|
528
|
+
if ':' in line:
|
|
529
|
+
line_num, content = line.split(':', 1)
|
|
530
|
+
preview_lines.append(f" {line_num}: {content.strip()}")
|
|
531
|
+
else:
|
|
532
|
+
preview_lines.append(f" {line.strip()}")
|
|
533
|
+
|
|
534
|
+
grep_results.append((file, preview_lines, match_count))
|
|
535
|
+
|
|
536
|
+
return sorted(grep_results)
|
|
537
|
+
|
|
538
|
+
except Exception as e:
|
|
539
|
+
print(f"Error running ag: {e}")
|
|
540
|
+
return []
|
|
541
|
+
|
|
542
|
+
def select_from_grep_results(
|
|
543
|
+
grep_results: List[Tuple[str, List[str], int]],
|
|
544
|
+
current_char_count: int
|
|
545
|
+
) -> Tuple[List[FileTuple], int]:
|
|
546
|
+
"""
|
|
547
|
+
Let user select from grep results.
|
|
548
|
+
Returns (selected_files, new_char_count).
|
|
549
|
+
"""
|
|
550
|
+
if not grep_results:
|
|
551
|
+
return [], current_char_count
|
|
552
|
+
|
|
553
|
+
print(f"\nFound {len(grep_results)} files:")
|
|
554
|
+
for i, (file_path, preview_lines, match_count) in enumerate(grep_results):
|
|
555
|
+
file_size = os.path.getsize(file_path)
|
|
556
|
+
file_size_readable = get_human_readable_size(file_size)
|
|
557
|
+
print(f"\n{i+1}. {os.path.relpath(file_path)} ({file_size_readable}) - {match_count} {'match' if match_count == 1 else 'matches'}")
|
|
558
|
+
for preview_line in preview_lines[:3]:
|
|
559
|
+
print(preview_line)
|
|
560
|
+
if match_count > 3:
|
|
561
|
+
print(f" ... and {match_count - 3} more matches")
|
|
562
|
+
|
|
563
|
+
while True:
|
|
564
|
+
print_char_count(current_char_count)
|
|
565
|
+
choice = input("\nSelect grep results: (a)ll / (n)one / (s)elect individually / numbers (e.g. 1,3-4) / (q)uit? ").lower()
|
|
566
|
+
|
|
567
|
+
selected_files: List[FileTuple] = []
|
|
568
|
+
char_count_delta = 0
|
|
569
|
+
|
|
570
|
+
if choice == 'a':
|
|
571
|
+
for file_path, _, _ in grep_results:
|
|
572
|
+
file_size = os.path.getsize(file_path)
|
|
573
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
574
|
+
char_count_delta += file_size
|
|
575
|
+
print(f"Added all {len(grep_results)} files from grep results.")
|
|
576
|
+
return selected_files, current_char_count + char_count_delta
|
|
577
|
+
|
|
578
|
+
elif choice == 'n':
|
|
579
|
+
print("Skipped all grep results.")
|
|
580
|
+
return [], current_char_count
|
|
581
|
+
|
|
582
|
+
elif choice == 'q':
|
|
583
|
+
print("Cancelled grep selection.")
|
|
584
|
+
return [], current_char_count
|
|
585
|
+
|
|
586
|
+
elif choice == 's':
|
|
587
|
+
for i, (file_path, preview_lines, match_count) in enumerate(grep_results):
|
|
588
|
+
file_size = os.path.getsize(file_path)
|
|
589
|
+
file_size_readable = get_human_readable_size(file_size)
|
|
590
|
+
file_char_estimate = file_size
|
|
591
|
+
file_token_estimate = file_char_estimate // 4
|
|
592
|
+
|
|
593
|
+
print(f"\n{os.path.relpath(file_path)} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens)")
|
|
594
|
+
print(f"{match_count} {'match' if match_count == 1 else 'matches'} for search pattern")
|
|
595
|
+
|
|
596
|
+
while True:
|
|
597
|
+
print_char_count(current_char_count + char_count_delta)
|
|
598
|
+
file_choice = input("(y)es / (n)o / (q)uit? ").lower()
|
|
599
|
+
|
|
600
|
+
if file_choice == 'y':
|
|
601
|
+
if is_large_file(file_path):
|
|
602
|
+
while True:
|
|
603
|
+
snippet_choice = input(f"File is large. Use (f)ull content or (s)nippet? ").lower()
|
|
604
|
+
if snippet_choice in ['f', 's']:
|
|
605
|
+
break
|
|
606
|
+
print("Invalid choice. Please enter 'f' or 's'.")
|
|
607
|
+
if snippet_choice == 's':
|
|
608
|
+
selected_files.append((file_path, True, None, get_language_for_file(file_path)))
|
|
609
|
+
char_count_delta += len(get_file_snippet(file_path))
|
|
610
|
+
else:
|
|
611
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
612
|
+
char_count_delta += file_size
|
|
613
|
+
else:
|
|
614
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
615
|
+
char_count_delta += file_size
|
|
616
|
+
print(f"Added: {os.path.relpath(file_path)}")
|
|
617
|
+
break
|
|
618
|
+
elif file_choice == 'n':
|
|
619
|
+
break
|
|
620
|
+
elif file_choice == 'q':
|
|
621
|
+
print(f"Added {len(selected_files)} files from grep results.")
|
|
622
|
+
return selected_files, current_char_count + char_count_delta
|
|
623
|
+
else:
|
|
624
|
+
print("Invalid choice. Please enter 'y', 'n', or 'q'.")
|
|
625
|
+
|
|
626
|
+
print(f"Added {len(selected_files)} files from grep results.")
|
|
627
|
+
return selected_files, current_char_count + char_count_delta
|
|
628
|
+
|
|
629
|
+
else:
|
|
630
|
+
# Try to parse number selection
|
|
631
|
+
try:
|
|
632
|
+
selected_indices = set()
|
|
633
|
+
parts = choice.replace(' ', '').split(',')
|
|
634
|
+
if all(p.strip() for p in parts):
|
|
635
|
+
for part in parts:
|
|
636
|
+
if '-' in part:
|
|
637
|
+
start_str, end_str = part.split('-', 1)
|
|
638
|
+
start = int(start_str)
|
|
639
|
+
end = int(end_str)
|
|
640
|
+
if start > end:
|
|
641
|
+
start, end = end, start
|
|
642
|
+
selected_indices.update(range(start - 1, end))
|
|
643
|
+
else:
|
|
644
|
+
selected_indices.add(int(part) - 1)
|
|
645
|
+
|
|
646
|
+
if all(0 <= i < len(grep_results) for i in selected_indices):
|
|
647
|
+
for i in sorted(selected_indices):
|
|
648
|
+
file_path, _, _ = grep_results[i]
|
|
649
|
+
file_size = os.path.getsize(file_path)
|
|
650
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
651
|
+
char_count_delta += file_size
|
|
652
|
+
print(f"Added {len(selected_files)} files from grep results.")
|
|
653
|
+
return selected_files, current_char_count + char_count_delta
|
|
654
|
+
else:
|
|
655
|
+
print(f"Error: Invalid number selection. Please choose numbers between 1 and {len(grep_results)}.")
|
|
656
|
+
else:
|
|
657
|
+
raise ValueError("Empty part detected in input.")
|
|
658
|
+
except ValueError:
|
|
659
|
+
print("Invalid choice. Please enter 'a', 'n', 's', 'q', or a list/range of numbers.")
|
|
660
|
+
|
|
661
|
+
def select_files_in_directory(directory: str, ignore_patterns: List[str], project_root_abs: str, current_char_count: int = 0, selected_files_set: Optional[Set[str]] = None) -> Tuple[List[FileTuple], int]:
|
|
662
|
+
if selected_files_set is None:
|
|
663
|
+
selected_files_set = set()
|
|
664
|
+
|
|
552
665
|
files = [f for f in os.listdir(directory)
|
|
553
666
|
if os.path.isfile(os.path.join(directory, f)) and not is_ignored(os.path.join(directory, f), ignore_patterns) and not is_binary(os.path.join(directory, f))]
|
|
554
667
|
|
|
@@ -563,17 +676,129 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], projec
|
|
|
563
676
|
file_size_readable = get_human_readable_size(file_size)
|
|
564
677
|
file_char_estimate = file_size # Assuming 1 byte ≈ 1 character for text files
|
|
565
678
|
file_token_estimate = file_char_estimate // 4
|
|
566
|
-
|
|
679
|
+
|
|
680
|
+
# Show if already selected
|
|
681
|
+
if os.path.abspath(file_path) in selected_files_set:
|
|
682
|
+
print(f"✓ {file} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens) [already selected]")
|
|
683
|
+
else:
|
|
684
|
+
print(f"- {file} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens)")
|
|
567
685
|
|
|
568
686
|
while True:
|
|
569
687
|
print_char_count(current_char_count)
|
|
570
|
-
choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
|
|
688
|
+
choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (g)rep / (q)uit? ").lower()
|
|
571
689
|
selected_files: List[FileTuple] = []
|
|
572
690
|
char_count_delta = 0
|
|
691
|
+
|
|
692
|
+
if choice == 'g':
|
|
693
|
+
# Grep functionality
|
|
694
|
+
pattern = input("\nEnter search pattern: ")
|
|
695
|
+
if pattern:
|
|
696
|
+
print(f"\nSearching in {directory} for '{pattern}'...")
|
|
697
|
+
grep_results = grep_files_in_directory(pattern, directory, ignore_patterns)
|
|
698
|
+
|
|
699
|
+
if not grep_results:
|
|
700
|
+
print(f"No files found matching '{pattern}'")
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
grep_selected, new_char_count = select_from_grep_results(grep_results, current_char_count)
|
|
704
|
+
|
|
705
|
+
if grep_selected:
|
|
706
|
+
selected_files.extend(grep_selected)
|
|
707
|
+
current_char_count = new_char_count
|
|
708
|
+
|
|
709
|
+
# Update selected files set
|
|
710
|
+
for file_tuple in grep_selected:
|
|
711
|
+
selected_files_set.add(os.path.abspath(file_tuple[0]))
|
|
712
|
+
|
|
713
|
+
# Analyze dependencies for grep-selected files
|
|
714
|
+
files_to_analyze = [f[0] for f in grep_selected]
|
|
715
|
+
for file_path in files_to_analyze:
|
|
716
|
+
new_deps, deps_char_count = _propose_and_add_dependencies(
|
|
717
|
+
file_path, project_root_abs, selected_files, current_char_count
|
|
718
|
+
)
|
|
719
|
+
selected_files.extend(new_deps)
|
|
720
|
+
current_char_count += deps_char_count
|
|
721
|
+
|
|
722
|
+
# Update selected files set with dependencies
|
|
723
|
+
for dep_tuple in new_deps:
|
|
724
|
+
selected_files_set.add(os.path.abspath(dep_tuple[0]))
|
|
725
|
+
|
|
726
|
+
print(f"\nReturning to directory: {directory}")
|
|
727
|
+
# Re-show the directory with updated selections
|
|
728
|
+
print("Files:")
|
|
729
|
+
for file in files:
|
|
730
|
+
file_path = os.path.join(directory, file)
|
|
731
|
+
file_size = os.path.getsize(file_path)
|
|
732
|
+
file_size_readable = get_human_readable_size(file_size)
|
|
733
|
+
if os.path.abspath(file_path) in selected_files_set:
|
|
734
|
+
print(f"✓ {file} ({file_size_readable}) [already selected]")
|
|
735
|
+
else:
|
|
736
|
+
print(f"- {file} ({file_size_readable})")
|
|
737
|
+
|
|
738
|
+
# Ask what to do with remaining files
|
|
739
|
+
remaining_files = [f for f in files if os.path.abspath(os.path.join(directory, f)) not in selected_files_set]
|
|
740
|
+
if remaining_files:
|
|
741
|
+
while True:
|
|
742
|
+
print_char_count(current_char_count)
|
|
743
|
+
remaining_choice = input("(y)es add remaining / (n)o skip remaining / (s)elect more / (g)rep again / (q)uit? ").lower()
|
|
744
|
+
if remaining_choice == 'y':
|
|
745
|
+
# Add all remaining files
|
|
746
|
+
for file in remaining_files:
|
|
747
|
+
file_path = os.path.join(directory, file)
|
|
748
|
+
file_size = os.path.getsize(file_path)
|
|
749
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
750
|
+
current_char_count += file_size
|
|
751
|
+
selected_files_set.add(os.path.abspath(file_path))
|
|
752
|
+
|
|
753
|
+
# Analyze dependencies for remaining files
|
|
754
|
+
for file in remaining_files:
|
|
755
|
+
file_path = os.path.join(directory, file)
|
|
756
|
+
new_deps, deps_char_count = _propose_and_add_dependencies(
|
|
757
|
+
file_path, project_root_abs, selected_files, current_char_count
|
|
758
|
+
)
|
|
759
|
+
selected_files.extend(new_deps)
|
|
760
|
+
current_char_count += deps_char_count
|
|
761
|
+
|
|
762
|
+
print(f"Added all remaining files from {directory}")
|
|
763
|
+
return selected_files, current_char_count
|
|
764
|
+
elif remaining_choice == 'n':
|
|
765
|
+
print(f"Skipped remaining files from {directory}")
|
|
766
|
+
return selected_files, current_char_count
|
|
767
|
+
elif remaining_choice == 's':
|
|
768
|
+
# Continue to individual selection
|
|
769
|
+
choice = 's'
|
|
770
|
+
break
|
|
771
|
+
elif remaining_choice == 'g':
|
|
772
|
+
# Continue to grep again
|
|
773
|
+
choice = 'g'
|
|
774
|
+
break
|
|
775
|
+
elif remaining_choice == 'q':
|
|
776
|
+
return selected_files, current_char_count
|
|
777
|
+
else:
|
|
778
|
+
print("Invalid choice. Please try again.")
|
|
779
|
+
|
|
780
|
+
if choice == 's':
|
|
781
|
+
# Fall through to individual selection
|
|
782
|
+
pass
|
|
783
|
+
elif choice == 'g':
|
|
784
|
+
# Loop back to grep
|
|
785
|
+
continue
|
|
786
|
+
else:
|
|
787
|
+
# No remaining files
|
|
788
|
+
return selected_files, current_char_count
|
|
789
|
+
else:
|
|
790
|
+
# No files selected from grep, continue
|
|
791
|
+
continue
|
|
792
|
+
else:
|
|
793
|
+
continue
|
|
794
|
+
|
|
573
795
|
if choice == 'y':
|
|
574
796
|
files_to_add_after_loop = []
|
|
575
797
|
for file in files:
|
|
576
798
|
file_path = os.path.join(directory, file)
|
|
799
|
+
if os.path.abspath(file_path) in selected_files_set:
|
|
800
|
+
continue # Skip already selected files
|
|
801
|
+
|
|
577
802
|
if is_large_file(file_path):
|
|
578
803
|
while True:
|
|
579
804
|
snippet_choice = input(f"{file} is large. Use (f)ull content or (s)nippet? ").lower()
|
|
@@ -600,12 +825,17 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], projec
|
|
|
600
825
|
|
|
601
826
|
print(f"Added all files from {directory}")
|
|
602
827
|
return selected_files, current_char_count
|
|
828
|
+
|
|
603
829
|
elif choice == 'n':
|
|
604
830
|
print(f"Ignored all files from {directory}")
|
|
605
831
|
return [], current_char_count
|
|
832
|
+
|
|
606
833
|
elif choice == 's':
|
|
607
834
|
for file in files:
|
|
608
835
|
file_path = os.path.join(directory, file)
|
|
836
|
+
if os.path.abspath(file_path) in selected_files_set:
|
|
837
|
+
continue # Skip already selected files
|
|
838
|
+
|
|
609
839
|
file_size = os.path.getsize(file_path)
|
|
610
840
|
file_size_readable = get_human_readable_size(file_size)
|
|
611
841
|
file_char_estimate = file_size
|
|
@@ -634,6 +864,7 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], projec
|
|
|
634
864
|
|
|
635
865
|
if file_to_add:
|
|
636
866
|
selected_files.append(file_to_add)
|
|
867
|
+
selected_files_set.add(os.path.abspath(file_path))
|
|
637
868
|
# Analyze dependencies immediately after adding
|
|
638
869
|
new_deps, deps_char_count = _propose_and_add_dependencies(file_path, project_root_abs, selected_files, current_char_count)
|
|
639
870
|
selected_files.extend(new_deps)
|
|
@@ -646,6 +877,7 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], projec
|
|
|
646
877
|
if chunks:
|
|
647
878
|
selected_files.append((file_path, False, chunks, get_language_for_file(file_path)))
|
|
648
879
|
current_char_count += char_count
|
|
880
|
+
selected_files_set.add(os.path.abspath(file_path))
|
|
649
881
|
break
|
|
650
882
|
elif file_choice == 'q':
|
|
651
883
|
print(f"Quitting selection for {directory}")
|
|
@@ -654,13 +886,18 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], projec
|
|
|
654
886
|
print("Invalid choice. Please enter 'y', 'n', 'p', or 'q'.")
|
|
655
887
|
print(f"Added {len(selected_files)} files from {directory}")
|
|
656
888
|
return selected_files, current_char_count
|
|
889
|
+
|
|
657
890
|
elif choice == 'q':
|
|
658
891
|
print(f"Quitting selection for {directory}")
|
|
659
892
|
return [], current_char_count
|
|
660
893
|
else:
|
|
661
894
|
print("Invalid choice. Please try again.")
|
|
662
895
|
|
|
663
|
-
|
|
896
|
+
|
|
897
|
+
def process_directory(directory: str, ignore_patterns: List[str], project_root_abs: str, current_char_count: int = 0, selected_files_set: Optional[Set[str]] = None) -> Tuple[List[FileTuple], Set[str], int]:
|
|
898
|
+
if selected_files_set is None:
|
|
899
|
+
selected_files_set = set()
|
|
900
|
+
|
|
664
901
|
files_to_include: List[FileTuple] = []
|
|
665
902
|
processed_dirs: Set[str] = set()
|
|
666
903
|
|
|
@@ -674,10 +911,16 @@ def process_directory(directory: str, ignore_patterns: List[str], project_root_a
|
|
|
674
911
|
print(f"\nExploring directory: {root}")
|
|
675
912
|
choice = input("(y)es explore / (n)o skip / (q)uit? ").lower()
|
|
676
913
|
if choice == 'y':
|
|
677
|
-
# Pass
|
|
678
|
-
selected_files, current_char_count = select_files_in_directory(
|
|
679
|
-
|
|
914
|
+
# Pass selected_files_set to track already selected files
|
|
915
|
+
selected_files, current_char_count = select_files_in_directory(
|
|
916
|
+
root, ignore_patterns, project_root_abs, current_char_count, selected_files_set
|
|
917
|
+
)
|
|
680
918
|
files_to_include.extend(selected_files)
|
|
919
|
+
|
|
920
|
+
# Update selected_files_set
|
|
921
|
+
for file_tuple in selected_files:
|
|
922
|
+
selected_files_set.add(os.path.abspath(file_tuple[0]))
|
|
923
|
+
|
|
681
924
|
processed_dirs.add(root)
|
|
682
925
|
elif choice == 'n':
|
|
683
926
|
dirs[:] = [] # Skip all subdirectories
|
|
@@ -722,112 +965,6 @@ def read_env_file():
|
|
|
722
965
|
env_vars[key.strip()] = value.strip()
|
|
723
966
|
return env_vars
|
|
724
967
|
|
|
725
|
-
def detect_env_variables(content, env_vars):
|
|
726
|
-
detected_vars = []
|
|
727
|
-
for key, value in env_vars.items():
|
|
728
|
-
if value in content:
|
|
729
|
-
detected_vars.append((key, value))
|
|
730
|
-
return detected_vars
|
|
731
|
-
|
|
732
|
-
def handle_env_variables(content, env_vars):
|
|
733
|
-
detected_vars = detect_env_variables(content, env_vars)
|
|
734
|
-
if not detected_vars:
|
|
735
|
-
return content
|
|
736
|
-
|
|
737
|
-
print("Detected environment variables:")
|
|
738
|
-
for key, value in detected_vars:
|
|
739
|
-
print(f"- {key}={value}")
|
|
740
|
-
|
|
741
|
-
for key, value in detected_vars:
|
|
742
|
-
while True:
|
|
743
|
-
choice = input(f"How would you like to handle {key}? (m)ask / (s)kip / (k)eep: ").lower()
|
|
744
|
-
if choice in ['m', 's', 'k']:
|
|
745
|
-
break
|
|
746
|
-
print("Invalid choice. Please enter 'm', 's', or 'k'.")
|
|
747
|
-
|
|
748
|
-
if choice == 'm':
|
|
749
|
-
content = content.replace(value, '*' * len(value))
|
|
750
|
-
elif choice == 's':
|
|
751
|
-
content = content.replace(value, "[REDACTED]")
|
|
752
|
-
# If 'k', we don't modify the content
|
|
753
|
-
|
|
754
|
-
return content
|
|
755
|
-
|
|
756
|
-
def generate_prompt_template(files_to_include: List[FileTuple], ignore_patterns: List[str], web_contents: Dict[str, Tuple[FileTuple, str]], env_vars: Dict[str, str]) -> Tuple[str, int]:
|
|
757
|
-
prompt = "# Project Overview\n\n"
|
|
758
|
-
prompt += "## Project Structure\n\n"
|
|
759
|
-
prompt += "```\n"
|
|
760
|
-
prompt += get_project_structure(ignore_patterns)
|
|
761
|
-
prompt += "\n```\n\n"
|
|
762
|
-
prompt += "## File Contents\n\n"
|
|
763
|
-
for file, use_snippet, chunks, content_type in files_to_include:
|
|
764
|
-
relative_path = get_relative_path(file)
|
|
765
|
-
language = content_type if content_type else get_language_for_file(file)
|
|
766
|
-
|
|
767
|
-
if chunks is not None:
|
|
768
|
-
prompt += f"### {relative_path} (selected patches)\n\n```{language}\n"
|
|
769
|
-
for chunk in chunks:
|
|
770
|
-
prompt += f"{chunk}\n"
|
|
771
|
-
prompt += "```\n\n"
|
|
772
|
-
elif use_snippet:
|
|
773
|
-
file_content = get_file_snippet(file)
|
|
774
|
-
prompt += f"### {relative_path} (snippet)\n\n```{language}\n{file_content}\n```\n\n"
|
|
775
|
-
else:
|
|
776
|
-
file_content = read_file_contents(file)
|
|
777
|
-
file_content = handle_env_variables(file_content, env_vars)
|
|
778
|
-
prompt += f"### {relative_path}\n\n```{language}\n{file_content}\n```\n\n"
|
|
779
|
-
|
|
780
|
-
if web_contents:
|
|
781
|
-
prompt += "## Web Content\n\n"
|
|
782
|
-
for url, (file_tuple, content) in web_contents.items():
|
|
783
|
-
_, is_snippet, _, content_type = file_tuple
|
|
784
|
-
content = handle_env_variables(content, env_vars)
|
|
785
|
-
language = content_type if content_type in ['json', 'csv'] else ''
|
|
786
|
-
prompt += f"### {url}{' (snippet)' if is_snippet else ''}\n\n```{language}\n{content}\n```\n\n"
|
|
787
|
-
|
|
788
|
-
prompt += "## Task Instructions\n\n"
|
|
789
|
-
cursor_position = len(prompt)
|
|
790
|
-
prompt += "\n\n"
|
|
791
|
-
prompt += "## Instructions for Achieving the Task\n\n"
|
|
792
|
-
analysis_text = (
|
|
793
|
-
"### Partnership Principles\n\n"
|
|
794
|
-
"We work as collaborative partners. You provide technical expertise and critical thinking. "
|
|
795
|
-
"I have exclusive access to my codebase, real environment, external services, and actual users. "
|
|
796
|
-
"Never assume project file contents - always ask to see them.\n\n"
|
|
797
|
-
"**Critical Thinking**: Challenge poor approaches, identify risks, suggest better alternatives. Don't be a yes-man.\n\n"
|
|
798
|
-
"**Anti-Hallucination**: Never write placeholder code for files in ## Project Structure. Use [STOP - NEED FILE: filename] and wait.\n\n"
|
|
799
|
-
"**Hard Stops**: End with [AWAITING USER RESPONSE] when you need input. Don't continue with assumptions.\n\n"
|
|
800
|
-
"### Development Workflow\n\n"
|
|
801
|
-
"We work in two modes:\n"
|
|
802
|
-
"- **Iterative Mode**: Build incrementally, show only changes\n"
|
|
803
|
-
"- **Consolidation Mode**: When I request, provide clean final version\n\n"
|
|
804
|
-
"1. **Understand & Analyze**:\n"
|
|
805
|
-
" - Rephrase task, identify issues, list needed files\n"
|
|
806
|
-
" - Challenge problematic aspects\n"
|
|
807
|
-
" - End: 'I need: [files]. Is this correct?' [AWAITING USER RESPONSE]\n\n"
|
|
808
|
-
"2. **Plan**:\n"
|
|
809
|
-
" - Present 2-3 approaches with pros/cons\n"
|
|
810
|
-
" - Recommend best approach\n"
|
|
811
|
-
" - End: 'Which approach?' [AWAITING USER RESPONSE]\n\n"
|
|
812
|
-
"3. **Implement Iteratively**:\n"
|
|
813
|
-
" - Small, testable increments\n"
|
|
814
|
-
" - Track failed attempts: `Attempt 1: [FAILED] X→Y (learned: Z)`\n"
|
|
815
|
-
" - After 3 failures, request diagnostics\n\n"
|
|
816
|
-
"4. **Code Presentation**:\n"
|
|
817
|
-
" - Always: `// FILE: path/to/file.ext`\n"
|
|
818
|
-
" - Iterative: Show only changes with context\n"
|
|
819
|
-
" - Consolidation: Smart choice - minimal changes = show patches, extensive = full file\n\n"
|
|
820
|
-
"5. **Test & Validate**:\n"
|
|
821
|
-
" - 'Test with: [command]. Share any errors.' [AWAITING USER RESPONSE]\n"
|
|
822
|
-
" - Include debug outputs\n"
|
|
823
|
-
" - May return to implementation based on results\n\n"
|
|
824
|
-
"### Permissions & Restrictions\n\n"
|
|
825
|
-
"**You MAY**: Request project files, ask me to test code/services, challenge my approach, refuse without info\n\n"
|
|
826
|
-
"**You MUST NOT**: Assume project file contents, continue past [AWAITING USER RESPONSE], be agreeable when you see problems\n"
|
|
827
|
-
)
|
|
828
|
-
prompt += analysis_text
|
|
829
|
-
return prompt, cursor_position
|
|
830
|
-
|
|
831
968
|
def open_editor_for_input(template: str, cursor_position: int) -> str:
|
|
832
969
|
editor = os.environ.get('EDITOR', 'vim')
|
|
833
970
|
with tempfile.NamedTemporaryFile(mode='w+', suffix='.md', delete=False) as temp_file:
|
|
@@ -864,14 +1001,16 @@ def main():
|
|
|
864
1001
|
env_vars = read_env_file()
|
|
865
1002
|
project_root_abs = os.path.abspath(os.getcwd())
|
|
866
1003
|
|
|
867
|
-
|
|
868
1004
|
files_to_include: List[FileTuple] = []
|
|
869
|
-
processed_dirs = set()
|
|
870
1005
|
web_contents: Dict[str, Tuple[FileTuple, str]] = {}
|
|
871
1006
|
current_char_count = 0
|
|
872
|
-
|
|
1007
|
+
|
|
1008
|
+
# Separate URLs from file/directory paths
|
|
1009
|
+
paths_for_tree = []
|
|
1010
|
+
|
|
873
1011
|
for input_path in args.inputs:
|
|
874
1012
|
if input_path.startswith(('http://', 'https://')):
|
|
1013
|
+
# Handle web content as before
|
|
875
1014
|
result = fetch_web_content(input_path)
|
|
876
1015
|
if result:
|
|
877
1016
|
file_tuple, full_content, snippet = result
|
|
@@ -905,89 +1044,37 @@ def main():
|
|
|
905
1044
|
current_char_count += len(content)
|
|
906
1045
|
print(f"Added {'snippet of ' if is_snippet else ''}web content from: {input_path}")
|
|
907
1046
|
print_char_count(current_char_count)
|
|
908
|
-
elif os.path.isfile(input_path):
|
|
909
|
-
abs_input_path = os.path.abspath(input_path)
|
|
910
|
-
if not is_ignored(abs_input_path, ignore_patterns) and not is_binary(abs_input_path):
|
|
911
|
-
file_size = os.path.getsize(abs_input_path)
|
|
912
|
-
file_size_readable = get_human_readable_size(file_size)
|
|
913
|
-
file_char_estimate = file_size
|
|
914
|
-
language = get_language_for_file(abs_input_path)
|
|
915
|
-
file_to_add = None
|
|
916
|
-
|
|
917
|
-
if is_large_file(abs_input_path):
|
|
918
|
-
print(f"\nFile {get_relative_path(abs_input_path)} ({file_size_readable}, ~{file_char_estimate} chars) is large.")
|
|
919
|
-
print("Preview (first ~50 lines or 4KB):")
|
|
920
|
-
print(get_colored_file_snippet(abs_input_path))
|
|
921
|
-
print("-" * 40)
|
|
922
|
-
while True:
|
|
923
|
-
print_char_count(current_char_count)
|
|
924
|
-
choice = input(f"How to include large file {get_relative_path(abs_input_path)}? (f)ull / (s)nippet / (p)atches / (n)o skip: ").lower()
|
|
925
|
-
if choice == 'f':
|
|
926
|
-
file_to_add = (abs_input_path, False, None, language)
|
|
927
|
-
current_char_count += file_char_estimate
|
|
928
|
-
print(f"Added full file: {get_relative_path(abs_input_path)}")
|
|
929
|
-
break
|
|
930
|
-
elif choice == 's':
|
|
931
|
-
snippet_content = get_file_snippet(abs_input_path)
|
|
932
|
-
file_to_add = (abs_input_path, True, None, language)
|
|
933
|
-
current_char_count += len(snippet_content)
|
|
934
|
-
print(f"Added snippet of file: {get_relative_path(abs_input_path)}")
|
|
935
|
-
break
|
|
936
|
-
elif choice == 'p':
|
|
937
|
-
chunks, char_count = select_file_patches(abs_input_path)
|
|
938
|
-
if chunks:
|
|
939
|
-
file_to_add = (abs_input_path, False, chunks, language)
|
|
940
|
-
current_char_count += char_count
|
|
941
|
-
print(f"Added selected patches from file: {get_relative_path(abs_input_path)}")
|
|
942
|
-
else:
|
|
943
|
-
print(f"No patches selected for {get_relative_path(abs_input_path)}. Skipping file.")
|
|
944
|
-
break
|
|
945
|
-
elif choice == 'n':
|
|
946
|
-
print(f"Skipped large file: {get_relative_path(abs_input_path)}")
|
|
947
|
-
break
|
|
948
|
-
else:
|
|
949
|
-
print("Invalid choice. Please enter 'f', 's', 'p', or 'n'.")
|
|
950
|
-
else:
|
|
951
|
-
file_to_add = (abs_input_path, False, None, language)
|
|
952
|
-
current_char_count += file_char_estimate
|
|
953
|
-
print(f"Added file: {get_relative_path(abs_input_path)} ({file_size_readable})")
|
|
954
|
-
|
|
955
|
-
if file_to_add:
|
|
956
|
-
files_to_include.append(file_to_add)
|
|
957
|
-
# --- NEW: Call dependency analysis ---
|
|
958
|
-
new_deps, deps_char_count = _propose_and_add_dependencies(abs_input_path, project_root_abs, files_to_include, current_char_count)
|
|
959
|
-
files_to_include.extend(new_deps)
|
|
960
|
-
current_char_count += deps_char_count
|
|
961
|
-
|
|
962
|
-
print_char_count(current_char_count)
|
|
963
|
-
|
|
964
|
-
else:
|
|
965
|
-
if is_ignored(input_path, ignore_patterns):
|
|
966
|
-
print(f"Ignoring file based on ignore patterns: {input_path}")
|
|
967
|
-
elif is_binary(input_path):
|
|
968
|
-
print(f"Ignoring binary file: {input_path}")
|
|
969
|
-
else:
|
|
970
|
-
print(f"Ignoring file: {input_path}")
|
|
971
|
-
elif os.path.isdir(input_path):
|
|
972
|
-
print(f"\nProcessing directory specified directly: {input_path}")
|
|
973
|
-
# Pass project_root_abs to process_directory
|
|
974
|
-
dir_files, dir_processed, current_char_count = process_directory(input_path, ignore_patterns, project_root_abs, current_char_count)
|
|
975
|
-
files_to_include.extend(dir_files)
|
|
976
|
-
processed_dirs.update(dir_processed)
|
|
977
1047
|
else:
|
|
978
|
-
|
|
1048
|
+
# Add to paths for tree selector
|
|
1049
|
+
if os.path.exists(input_path):
|
|
1050
|
+
paths_for_tree.append(input_path)
|
|
1051
|
+
else:
|
|
1052
|
+
print(f"Warning: {input_path} does not exist. Skipping.")
|
|
1053
|
+
|
|
1054
|
+
# Use tree selector for file/directory selection
|
|
1055
|
+
if paths_for_tree:
|
|
1056
|
+
print("\nStarting interactive file selection...")
|
|
1057
|
+
print("Use arrow keys to navigate, Space to select, 'q' to finish. Press 'h' for help.\n")
|
|
1058
|
+
|
|
1059
|
+
tree_selector = TreeSelector(ignore_patterns, project_root_abs)
|
|
1060
|
+
try:
|
|
1061
|
+
selected_files, file_char_count = tree_selector.run(paths_for_tree)
|
|
1062
|
+
files_to_include.extend(selected_files)
|
|
1063
|
+
current_char_count += file_char_count
|
|
1064
|
+
except KeyboardInterrupt:
|
|
1065
|
+
print("\nSelection cancelled.")
|
|
1066
|
+
return
|
|
979
1067
|
|
|
980
1068
|
if not files_to_include and not web_contents:
|
|
981
1069
|
print("No files or web content were selected. Exiting.")
|
|
982
1070
|
return
|
|
983
1071
|
|
|
984
1072
|
print("\nFile and web content selection complete.")
|
|
985
|
-
print_char_count(current_char_count)
|
|
1073
|
+
print_char_count(current_char_count)
|
|
986
1074
|
|
|
987
1075
|
added_files_count = len(files_to_include)
|
|
988
|
-
added_dirs_count = len(processed_dirs)
|
|
989
1076
|
added_web_count = len(web_contents)
|
|
990
|
-
print(f"Summary: Added {added_files_count} files/patches
|
|
1077
|
+
print(f"Summary: Added {added_files_count} files/patches and {added_web_count} web sources.")
|
|
991
1078
|
|
|
992
1079
|
prompt_template, cursor_position = generate_prompt_template(files_to_include, ignore_patterns, web_contents, env_vars)
|
|
993
1080
|
|