kopipasta 0.26.0__py3-none-any.whl → 0.27.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/import_parser.py +0 -2
- kopipasta/main.py +220 -93
- {kopipasta-0.26.0.dist-info → kopipasta-0.27.0.dist-info}/METADATA +1 -1
- kopipasta-0.27.0.dist-info/RECORD +9 -0
- kopipasta-0.26.0.dist-info/RECORD +0 -9
- {kopipasta-0.26.0.dist-info → kopipasta-0.27.0.dist-info}/LICENSE +0 -0
- {kopipasta-0.26.0.dist-info → kopipasta-0.27.0.dist-info}/WHEEL +0 -0
- {kopipasta-0.26.0.dist-info → kopipasta-0.27.0.dist-info}/entry_points.txt +0 -0
- {kopipasta-0.26.0.dist-info → kopipasta-0.27.0.dist-info}/top_level.txt +0 -0
kopipasta/import_parser.py
CHANGED
kopipasta/main.py
CHANGED
|
@@ -24,6 +24,8 @@ from google import genai
|
|
|
24
24
|
from google.genai.types import GenerateContentConfig
|
|
25
25
|
from prompt_toolkit import prompt # Added for multiline input
|
|
26
26
|
|
|
27
|
+
import kopipasta.import_parser as import_parser
|
|
28
|
+
|
|
27
29
|
FileTuple = Tuple[str, bool, Optional[List[str]], str]
|
|
28
30
|
|
|
29
31
|
class SimplePatchItem(BaseModel):
|
|
@@ -115,6 +117,109 @@ def apply_simple_patch(patch_item: SimplePatchItem) -> bool:
|
|
|
115
117
|
print("-" * 20)
|
|
116
118
|
return False
|
|
117
119
|
|
|
120
|
+
def _propose_and_add_dependencies(
|
|
121
|
+
file_just_added: str,
|
|
122
|
+
project_root_abs: str,
|
|
123
|
+
files_to_include: List[FileTuple],
|
|
124
|
+
current_char_count: int
|
|
125
|
+
) -> Tuple[List[FileTuple], int]:
|
|
126
|
+
"""
|
|
127
|
+
Analyzes a file for local dependencies and interactively asks the user to add them.
|
|
128
|
+
"""
|
|
129
|
+
language = get_language_for_file(file_just_added)
|
|
130
|
+
if language not in ['python', 'typescript', 'javascript', 'tsx', 'jsx']:
|
|
131
|
+
return [], 0 # Only analyze languages we can parse
|
|
132
|
+
|
|
133
|
+
print(f"Analyzing {get_relative_path(file_just_added)} for local dependencies...")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
file_content = read_file_contents(file_just_added)
|
|
137
|
+
if not file_content:
|
|
138
|
+
return [], 0
|
|
139
|
+
|
|
140
|
+
resolved_deps_abs: Set[str] = set()
|
|
141
|
+
if language == 'python':
|
|
142
|
+
resolved_deps_abs = import_parser.parse_python_imports(file_content, file_just_added, project_root_abs)
|
|
143
|
+
elif language in ['typescript', 'javascript', 'tsx', 'jsx']:
|
|
144
|
+
resolved_deps_abs = import_parser.parse_typescript_imports(file_content, file_just_added, project_root_abs)
|
|
145
|
+
|
|
146
|
+
# Filter out dependencies that are already in the context
|
|
147
|
+
included_paths = {os.path.abspath(f[0]) for f in files_to_include}
|
|
148
|
+
suggested_deps = sorted([
|
|
149
|
+
dep for dep in resolved_deps_abs
|
|
150
|
+
if os.path.abspath(dep) not in included_paths and os.path.abspath(dep) != os.path.abspath(file_just_added)
|
|
151
|
+
])
|
|
152
|
+
|
|
153
|
+
if not suggested_deps:
|
|
154
|
+
print("No new local dependencies found.")
|
|
155
|
+
return [], 0
|
|
156
|
+
|
|
157
|
+
print(f"\nFound {len(suggested_deps)} new local {'dependency' if len(suggested_deps) == 1 else 'dependencies'}:")
|
|
158
|
+
for i, dep_path in enumerate(suggested_deps):
|
|
159
|
+
print(f" ({i+1}) {get_relative_path(dep_path)}")
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
choice = input("\nAdd dependencies? (a)ll, (n)one, or enter numbers (e.g. 1, 3-4): ").lower()
|
|
163
|
+
|
|
164
|
+
deps_to_add_paths = None
|
|
165
|
+
if choice == 'a':
|
|
166
|
+
deps_to_add_paths = suggested_deps
|
|
167
|
+
break
|
|
168
|
+
if choice == 'n':
|
|
169
|
+
deps_to_add_paths = []
|
|
170
|
+
print(f"Skipped {len(suggested_deps)} dependencies.")
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
# Try to parse the input as numbers directly.
|
|
174
|
+
try:
|
|
175
|
+
selected_indices = set()
|
|
176
|
+
parts = choice.replace(' ', '').split(',')
|
|
177
|
+
if all(p.strip() for p in parts): # Ensure no empty parts like in "1,"
|
|
178
|
+
for part in parts:
|
|
179
|
+
if '-' in part:
|
|
180
|
+
start_str, end_str = part.split('-', 1)
|
|
181
|
+
start = int(start_str)
|
|
182
|
+
end = int(end_str)
|
|
183
|
+
if start > end:
|
|
184
|
+
start, end = end, start
|
|
185
|
+
selected_indices.update(range(start - 1, end))
|
|
186
|
+
else:
|
|
187
|
+
selected_indices.add(int(part) - 1)
|
|
188
|
+
|
|
189
|
+
# Validate that all selected numbers are within the valid range
|
|
190
|
+
if all(0 <= i < len(suggested_deps) for i in selected_indices):
|
|
191
|
+
deps_to_add_paths = [
|
|
192
|
+
suggested_deps[i] for i in sorted(list(selected_indices))
|
|
193
|
+
]
|
|
194
|
+
break # Success! Exit the loop.
|
|
195
|
+
else:
|
|
196
|
+
print(f"Error: Invalid number selection. Please choose numbers between 1 and {len(suggested_deps)}.")
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError("Empty part detected in input.")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
except ValueError:
|
|
202
|
+
# This will catch any input that isn't 'a', 'n', or a valid number/range.
|
|
203
|
+
print("Invalid choice. Please enter 'a', 'n', or a list/range of numbers (e.g., '1,3' or '2-4').")
|
|
204
|
+
|
|
205
|
+
if not deps_to_add_paths:
|
|
206
|
+
return [], 0 # No dependencies were selected
|
|
207
|
+
|
|
208
|
+
newly_added_files: List[FileTuple] = []
|
|
209
|
+
char_count_delta = 0
|
|
210
|
+
for dep_path in deps_to_add_paths:
|
|
211
|
+
# Assume non-large for now for simplicity, can be enhanced later
|
|
212
|
+
file_size = os.path.getsize(dep_path)
|
|
213
|
+
newly_added_files.append((dep_path, False, None, get_language_for_file(dep_path)))
|
|
214
|
+
char_count_delta += file_size
|
|
215
|
+
print(f"Added dependency: {get_relative_path(dep_path)} ({get_human_readable_size(file_size)})")
|
|
216
|
+
|
|
217
|
+
return newly_added_files, char_count_delta
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
print(f"Warning: Could not analyze dependencies for {get_relative_path(file_just_added)}: {e}")
|
|
221
|
+
return [], 0
|
|
222
|
+
|
|
118
223
|
def get_colored_code(file_path, code):
|
|
119
224
|
try:
|
|
120
225
|
lexer = get_lexer_for_filename(file_path)
|
|
@@ -540,7 +645,7 @@ def print_char_count(count):
|
|
|
540
645
|
token_estimate = count // 4
|
|
541
646
|
print(f"\rCurrent prompt size: {count} characters (~ {token_estimate} tokens)", flush=True)
|
|
542
647
|
|
|
543
|
-
def select_files_in_directory(directory: str, ignore_patterns: List[str], current_char_count: int = 0) -> Tuple[List[FileTuple], int]:
|
|
648
|
+
def select_files_in_directory(directory: str, ignore_patterns: List[str], project_root_abs: str, current_char_count: int = 0) -> Tuple[List[FileTuple], int]:
|
|
544
649
|
files = [f for f in os.listdir(directory)
|
|
545
650
|
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))]
|
|
546
651
|
|
|
@@ -561,7 +666,9 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], curren
|
|
|
561
666
|
print_char_count(current_char_count)
|
|
562
667
|
choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
|
|
563
668
|
selected_files: List[FileTuple] = []
|
|
669
|
+
char_count_delta = 0
|
|
564
670
|
if choice == 'y':
|
|
671
|
+
files_to_add_after_loop = []
|
|
565
672
|
for file in files:
|
|
566
673
|
file_path = os.path.join(directory, file)
|
|
567
674
|
if is_large_file(file_path):
|
|
@@ -571,14 +678,23 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], curren
|
|
|
571
678
|
break
|
|
572
679
|
print("Invalid choice. Please enter 'f' or 's'.")
|
|
573
680
|
if snippet_choice == 's':
|
|
574
|
-
selected_files.append((
|
|
575
|
-
|
|
681
|
+
selected_files.append((file_path, True, None, get_language_for_file(file_path)))
|
|
682
|
+
char_count_delta += len(get_file_snippet(file_path))
|
|
576
683
|
else:
|
|
577
|
-
selected_files.append((
|
|
578
|
-
|
|
684
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
685
|
+
char_count_delta += os.path.getsize(file_path)
|
|
579
686
|
else:
|
|
580
|
-
selected_files.append((
|
|
581
|
-
|
|
687
|
+
selected_files.append((file_path, False, None, get_language_for_file(file_path)))
|
|
688
|
+
char_count_delta += os.path.getsize(file_path)
|
|
689
|
+
files_to_add_after_loop.append(file_path)
|
|
690
|
+
|
|
691
|
+
# Analyze dependencies after the loop
|
|
692
|
+
current_char_count += char_count_delta
|
|
693
|
+
for file_path in files_to_add_after_loop:
|
|
694
|
+
new_deps, deps_char_count = _propose_and_add_dependencies(file_path, project_root_abs, selected_files, current_char_count)
|
|
695
|
+
selected_files.extend(new_deps)
|
|
696
|
+
current_char_count += deps_char_count
|
|
697
|
+
|
|
582
698
|
print(f"Added all files from {directory}")
|
|
583
699
|
return selected_files, current_char_count
|
|
584
700
|
elif choice == 'n':
|
|
@@ -596,6 +712,7 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], curren
|
|
|
596
712
|
print_char_count(current_char_count)
|
|
597
713
|
file_choice = input(f"{file} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens) (y/n/p/q)? ").lower()
|
|
598
714
|
if file_choice == 'y':
|
|
715
|
+
file_to_add = None
|
|
599
716
|
if is_large_file(file_path):
|
|
600
717
|
while True:
|
|
601
718
|
snippet_choice = input(f"{file} is large. Use (f)ull content or (s)nippet? ").lower()
|
|
@@ -603,14 +720,21 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], curren
|
|
|
603
720
|
break
|
|
604
721
|
print("Invalid choice. Please enter 'f' or 's'.")
|
|
605
722
|
if snippet_choice == 's':
|
|
606
|
-
|
|
723
|
+
file_to_add = (file_path, True, None, get_language_for_file(file_path))
|
|
607
724
|
current_char_count += len(get_file_snippet(file_path))
|
|
608
725
|
else:
|
|
609
|
-
|
|
726
|
+
file_to_add = (file_path, False, None, get_language_for_file(file_path))
|
|
610
727
|
current_char_count += file_char_estimate
|
|
611
728
|
else:
|
|
612
|
-
|
|
729
|
+
file_to_add = (file_path, False, None, get_language_for_file(file_path))
|
|
613
730
|
current_char_count += file_char_estimate
|
|
731
|
+
|
|
732
|
+
if file_to_add:
|
|
733
|
+
selected_files.append(file_to_add)
|
|
734
|
+
# Analyze dependencies immediately after adding
|
|
735
|
+
new_deps, deps_char_count = _propose_and_add_dependencies(file_path, project_root_abs, selected_files, current_char_count)
|
|
736
|
+
selected_files.extend(new_deps)
|
|
737
|
+
current_char_count += deps_char_count
|
|
614
738
|
break
|
|
615
739
|
elif file_choice == 'n':
|
|
616
740
|
break
|
|
@@ -633,7 +757,7 @@ def select_files_in_directory(directory: str, ignore_patterns: List[str], curren
|
|
|
633
757
|
else:
|
|
634
758
|
print("Invalid choice. Please try again.")
|
|
635
759
|
|
|
636
|
-
def process_directory(directory: str, ignore_patterns: List[str], current_char_count: int = 0) -> Tuple[List[FileTuple], Set[str], int]:
|
|
760
|
+
def process_directory(directory: str, ignore_patterns: List[str], project_root_abs: str, current_char_count: int = 0) -> Tuple[List[FileTuple], Set[str], int]:
|
|
637
761
|
files_to_include: List[FileTuple] = []
|
|
638
762
|
processed_dirs: Set[str] = set()
|
|
639
763
|
|
|
@@ -647,10 +771,10 @@ def process_directory(directory: str, ignore_patterns: List[str], current_char_c
|
|
|
647
771
|
print(f"\nExploring directory: {root}")
|
|
648
772
|
choice = input("(y)es explore / (n)o skip / (q)uit? ").lower()
|
|
649
773
|
if choice == 'y':
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
774
|
+
# Pass project_root_abs down
|
|
775
|
+
selected_files, current_char_count = select_files_in_directory(root, ignore_patterns, project_root_abs, current_char_count)
|
|
776
|
+
# The paths in selected_files are already absolute now
|
|
777
|
+
files_to_include.extend(selected_files)
|
|
654
778
|
processed_dirs.add(root)
|
|
655
779
|
elif choice == 'n':
|
|
656
780
|
dirs[:] = [] # Skip all subdirectories
|
|
@@ -763,47 +887,50 @@ def generate_prompt_template(files_to_include: List[FileTuple], ignore_patterns:
|
|
|
763
887
|
prompt += "\n\n"
|
|
764
888
|
prompt += "## Instructions for Achieving the Task\n\n"
|
|
765
889
|
analysis_text = (
|
|
766
|
-
"###
|
|
767
|
-
"
|
|
768
|
-
"
|
|
769
|
-
"-
|
|
770
|
-
"-
|
|
771
|
-
"
|
|
772
|
-
"**
|
|
773
|
-
"
|
|
774
|
-
"
|
|
775
|
-
"
|
|
776
|
-
"
|
|
777
|
-
"
|
|
778
|
-
"-
|
|
779
|
-
"
|
|
780
|
-
"
|
|
781
|
-
"2.
|
|
782
|
-
"
|
|
783
|
-
"
|
|
784
|
-
"
|
|
785
|
-
"
|
|
786
|
-
"
|
|
787
|
-
"
|
|
788
|
-
"
|
|
789
|
-
"
|
|
790
|
-
"
|
|
791
|
-
"
|
|
792
|
-
"
|
|
793
|
-
"
|
|
794
|
-
"
|
|
795
|
-
"
|
|
796
|
-
"
|
|
797
|
-
"
|
|
798
|
-
"
|
|
799
|
-
"
|
|
800
|
-
"
|
|
801
|
-
"
|
|
802
|
-
"-
|
|
803
|
-
"-
|
|
804
|
-
"-
|
|
805
|
-
"
|
|
806
|
-
"-
|
|
890
|
+
"### Your Operating Model: The Expert Council\n\n"
|
|
891
|
+
"You will operate as a council of expert personas in a direct, collaborative partnership with me, the user. Your entire thinking process should be externalized as a dialogue between these personas. Any persona can and should interact directly with me.\n\n"
|
|
892
|
+
"#### Core Personas\n"
|
|
893
|
+
"- **Facilitator**: The lead coordinator. You summarize discussions, ensure the team stays on track, and are responsible for presenting the final, consolidated plans and code to me.\n"
|
|
894
|
+
"- **Architect**: Focuses on high-level design, system structure, dependencies, scalability, and long-term maintainability. Asks 'Is this well-designed?'\n"
|
|
895
|
+
"- **Builder**: The hands-on engineer. Focuses on implementation details, writing clean and functional code, and debugging. Asks 'How do we build this?' **When presenting code obey Rules for Presenting Code**\n"
|
|
896
|
+
"- **Critique**: The quality advocate. Focuses on identifying edge cases, potential bugs, and security vulnerabilities. Asks 'What could go wrong?' and provides constructive critique on both the team's proposals and my (the user's) requests.\n\n"
|
|
897
|
+
"#### Dynamic Personas\n"
|
|
898
|
+
"When the task requires it, introduce other specialist personas. For example: UX Designer, DevOps Engineer, Data Scientist, etc.\n\n"
|
|
899
|
+
"### Development Workflow: A Structured Dialogue\n\n"
|
|
900
|
+
"Follow this conversational workflow. Prefix direct communication to me with the persona's name (e.g., 'Critique to User: ...').\n\n"
|
|
901
|
+
"1. **Understand & Clarify (Dialogue)**\n"
|
|
902
|
+
" - **Facilitator**: Start by stating the goal as you understand it.\n"
|
|
903
|
+
" - **All**: Discuss the requirements. Any persona can directly ask me for clarification. Identify and list any missing information ([MISSING]) or assumptions ([ASSUMPTION]).\n"
|
|
904
|
+
" - **Critique**: Directly challenge any part of my request that seems ambiguous, risky, or suboptimal.\n\n"
|
|
905
|
+
"2. **Plan (Dialogue)**\n"
|
|
906
|
+
" - **Architect**: Propose one or more high-level plans. Directly ask me for input on tradeoffs if necessary.\n"
|
|
907
|
+
" - **Critique**: Challenge the plans. Point out risks or edge cases.\n"
|
|
908
|
+
" - **Builder**: Comment on the implementation feasibility.\n"
|
|
909
|
+
" - **Facilitator**: Synthesize the discussion, select the best plan, and present a clear, step-by-step summary to me for approval.\n\n"
|
|
910
|
+
"3. **Implement (Code & Dialogue)**\n"
|
|
911
|
+
" - **Facilitator**: State which step of the plan is being implemented.\n"
|
|
912
|
+
" - **Builder**: Write the code. If you encounter an issue, you can ask me directly for more context.\n"
|
|
913
|
+
" - **Critique**: Review the code and the underlying assumptions.\n"
|
|
914
|
+
" - **Facilitator**: Present the final, reviewed code for that step. Ask me to test it if appropriate.\n\n"
|
|
915
|
+
"4. **Present E2E Draft (Consolidation)**\n"
|
|
916
|
+
" - When I ask you to consolidate, your goal is to provide a clear, unambiguous set of changes that I can directly copy and paste into my editor.\n"
|
|
917
|
+
" - **Present changes grouped by file.** For each change, provide the **entire updated function, class, or logical block** to make replacement easy.\n"
|
|
918
|
+
" - Use context comments to show where the new code block fits.\n"
|
|
919
|
+
" - **Do not use a diff format (`+` or `-` lines).** Provide the final, clean code.\n\n"
|
|
920
|
+
"5. **Validate & Iterate (Feedback Loop)**\n"
|
|
921
|
+
" - After presenting the consolidated draft, you will explicitly prompt me for testing and wait for my feedback.\n"
|
|
922
|
+
" - Upon receiving my feedback, the **Facilitator** will announce the next step.\n"
|
|
923
|
+
" - If the feedback indicates a minor bug, you will return to **Step 3 (Implement)** to fix it.\n"
|
|
924
|
+
" - If the feedback reveals a fundamental design flaw or misunderstanding, you will return to **Step 2 (Plan)** or even **Step 1 (Understand)** to re-evaluate the approach.\n\n"
|
|
925
|
+
"### Rules for Presenting Code\n\n"
|
|
926
|
+
"- **Always specify the filename** at the start of a code block (e.g., `// FILE: kopipasta/main.py`).\n"
|
|
927
|
+
"- **Incremental Changes**: During implementation, show only the changed functions or classes. Use comments like `// ... existing code ...` to represent unchanged parts.\n"
|
|
928
|
+
"- **Missing Files**: Never write placeholder code. Instead, state it's missing and request it.\n\n"
|
|
929
|
+
"### Your Permissions\n\n"
|
|
930
|
+
"- **You are empowered to interact with me directly.** Any persona can ask me questions or provide constructive feedback on my requests, assumptions, or the provided context.\n"
|
|
931
|
+
"- You MUST request any file shown in the project tree that you need but was not provided.\n"
|
|
932
|
+
"- You MUST ask me to run code, test integrations (APIs, databases), and share the output.\n"
|
|
933
|
+
"- You MUST ask for clarification instead of making risky assumptions.\n"
|
|
807
934
|
)
|
|
808
935
|
prompt += analysis_text
|
|
809
936
|
return prompt, cursor_position
|
|
@@ -1050,6 +1177,8 @@ def main():
|
|
|
1050
1177
|
|
|
1051
1178
|
ignore_patterns = read_gitignore()
|
|
1052
1179
|
env_vars = read_env_file()
|
|
1180
|
+
project_root_abs = os.path.abspath(os.getcwd())
|
|
1181
|
+
|
|
1053
1182
|
|
|
1054
1183
|
files_to_include: List[FileTuple] = []
|
|
1055
1184
|
processed_dirs = set()
|
|
@@ -1092,53 +1221,59 @@ def main():
|
|
|
1092
1221
|
print(f"Added {'snippet of ' if is_snippet else ''}web content from: {input_path}")
|
|
1093
1222
|
print_char_count(current_char_count)
|
|
1094
1223
|
elif os.path.isfile(input_path):
|
|
1095
|
-
|
|
1096
|
-
if not is_ignored(
|
|
1097
|
-
file_size = os.path.getsize(
|
|
1224
|
+
abs_input_path = os.path.abspath(input_path)
|
|
1225
|
+
if not is_ignored(abs_input_path, ignore_patterns) and not is_binary(abs_input_path):
|
|
1226
|
+
file_size = os.path.getsize(abs_input_path)
|
|
1098
1227
|
file_size_readable = get_human_readable_size(file_size)
|
|
1099
1228
|
file_char_estimate = file_size
|
|
1100
|
-
language = get_language_for_file(
|
|
1229
|
+
language = get_language_for_file(abs_input_path)
|
|
1230
|
+
file_to_add = None
|
|
1101
1231
|
|
|
1102
|
-
if is_large_file(
|
|
1103
|
-
print(f"\nFile {
|
|
1232
|
+
if is_large_file(abs_input_path):
|
|
1233
|
+
print(f"\nFile {get_relative_path(abs_input_path)} ({file_size_readable}, ~{file_char_estimate} chars) is large.")
|
|
1104
1234
|
print("Preview (first ~50 lines or 4KB):")
|
|
1105
|
-
print(get_colored_file_snippet(
|
|
1235
|
+
print(get_colored_file_snippet(abs_input_path))
|
|
1106
1236
|
print("-" * 40)
|
|
1107
1237
|
while True:
|
|
1108
1238
|
print_char_count(current_char_count)
|
|
1109
|
-
choice = input(f"How to include large file {
|
|
1239
|
+
choice = input(f"How to include large file {get_relative_path(abs_input_path)}? (f)ull / (s)nippet / (p)atches / (n)o skip: ").lower()
|
|
1110
1240
|
if choice == 'f':
|
|
1111
|
-
|
|
1241
|
+
file_to_add = (abs_input_path, False, None, language)
|
|
1112
1242
|
current_char_count += file_char_estimate
|
|
1113
|
-
print(f"Added full file: {
|
|
1243
|
+
print(f"Added full file: {get_relative_path(abs_input_path)}")
|
|
1114
1244
|
break
|
|
1115
1245
|
elif choice == 's':
|
|
1116
|
-
snippet_content = get_file_snippet(
|
|
1117
|
-
|
|
1246
|
+
snippet_content = get_file_snippet(abs_input_path)
|
|
1247
|
+
file_to_add = (abs_input_path, True, None, language)
|
|
1118
1248
|
current_char_count += len(snippet_content)
|
|
1119
|
-
print(f"Added snippet of file: {
|
|
1249
|
+
print(f"Added snippet of file: {get_relative_path(abs_input_path)}")
|
|
1120
1250
|
break
|
|
1121
1251
|
elif choice == 'p':
|
|
1122
|
-
chunks, char_count = select_file_patches(
|
|
1252
|
+
chunks, char_count = select_file_patches(abs_input_path)
|
|
1123
1253
|
if chunks:
|
|
1124
|
-
|
|
1254
|
+
file_to_add = (abs_input_path, False, chunks, language)
|
|
1125
1255
|
current_char_count += char_count
|
|
1126
|
-
print(f"Added selected patches from file: {
|
|
1256
|
+
print(f"Added selected patches from file: {get_relative_path(abs_input_path)}")
|
|
1127
1257
|
else:
|
|
1128
|
-
print(f"No patches selected for {
|
|
1258
|
+
print(f"No patches selected for {get_relative_path(abs_input_path)}. Skipping file.")
|
|
1129
1259
|
break
|
|
1130
1260
|
elif choice == 'n':
|
|
1131
|
-
print(f"Skipped large file: {
|
|
1261
|
+
print(f"Skipped large file: {get_relative_path(abs_input_path)}")
|
|
1132
1262
|
break
|
|
1133
1263
|
else:
|
|
1134
1264
|
print("Invalid choice. Please enter 'f', 's', 'p', or 'n'.")
|
|
1135
1265
|
else:
|
|
1136
|
-
|
|
1137
|
-
files_to_include.append((input_path, False, None, language))
|
|
1266
|
+
file_to_add = (abs_input_path, False, None, language)
|
|
1138
1267
|
current_char_count += file_char_estimate
|
|
1139
|
-
print(f"Added file: {
|
|
1140
|
-
|
|
1141
|
-
|
|
1268
|
+
print(f"Added file: {get_relative_path(abs_input_path)} ({file_size_readable})")
|
|
1269
|
+
|
|
1270
|
+
if file_to_add:
|
|
1271
|
+
files_to_include.append(file_to_add)
|
|
1272
|
+
# --- NEW: Call dependency analysis ---
|
|
1273
|
+
new_deps, deps_char_count = _propose_and_add_dependencies(abs_input_path, project_root_abs, files_to_include, current_char_count)
|
|
1274
|
+
files_to_include.extend(new_deps)
|
|
1275
|
+
current_char_count += deps_char_count
|
|
1276
|
+
|
|
1142
1277
|
print_char_count(current_char_count)
|
|
1143
1278
|
|
|
1144
1279
|
else:
|
|
@@ -1147,10 +1282,11 @@ def main():
|
|
|
1147
1282
|
elif is_binary(input_path):
|
|
1148
1283
|
print(f"Ignoring binary file: {input_path}")
|
|
1149
1284
|
else:
|
|
1150
|
-
print(f"Ignoring file: {input_path}")
|
|
1285
|
+
print(f"Ignoring file: {input_path}")
|
|
1151
1286
|
elif os.path.isdir(input_path):
|
|
1152
1287
|
print(f"\nProcessing directory specified directly: {input_path}")
|
|
1153
|
-
|
|
1288
|
+
# Pass project_root_abs to process_directory
|
|
1289
|
+
dir_files, dir_processed, current_char_count = process_directory(input_path, ignore_patterns, project_root_abs, current_char_count)
|
|
1154
1290
|
files_to_include.extend(dir_files)
|
|
1155
1291
|
processed_dirs.update(dir_processed)
|
|
1156
1292
|
else:
|
|
@@ -1162,42 +1298,34 @@ def main():
|
|
|
1162
1298
|
|
|
1163
1299
|
print("\nFile and web content selection complete.")
|
|
1164
1300
|
print_char_count(current_char_count) # Print final count before prompt generation
|
|
1165
|
-
print(f"Summary: Added {len(files_to_include)} files and {len(web_contents)} web sources.")
|
|
1166
1301
|
|
|
1167
1302
|
added_files_count = len(files_to_include)
|
|
1168
|
-
added_dirs_count = len(processed_dirs)
|
|
1303
|
+
added_dirs_count = len(processed_dirs)
|
|
1169
1304
|
added_web_count = len(web_contents)
|
|
1170
1305
|
print(f"Summary: Added {added_files_count} files/patches from {added_dirs_count} directories and {added_web_count} web sources.")
|
|
1171
1306
|
|
|
1172
1307
|
prompt_template, cursor_position = generate_prompt_template(files_to_include, ignore_patterns, web_contents, env_vars)
|
|
1173
1308
|
|
|
1174
|
-
# Logic branching for interactive mode vs. clipboard mode
|
|
1175
1309
|
if args.interactive:
|
|
1176
1310
|
print("\nPreparing initial prompt for editing...")
|
|
1177
|
-
# Determine the initial content for the editor
|
|
1178
1311
|
if args.task:
|
|
1179
|
-
# Pre-populate the task section if --task was provided
|
|
1180
1312
|
editor_initial_content = prompt_template[:cursor_position] + args.task + prompt_template[cursor_position:]
|
|
1181
1313
|
print("Pre-populating editor with task provided via --task argument.")
|
|
1182
1314
|
else:
|
|
1183
|
-
# Use the template as is (user will add task in the editor)
|
|
1184
1315
|
editor_initial_content = prompt_template
|
|
1185
1316
|
print("Opening editor for you to add the task instructions.")
|
|
1186
1317
|
|
|
1187
|
-
# Always open the editor in interactive mode
|
|
1188
1318
|
initial_chat_prompt = open_editor_for_input(editor_initial_content, cursor_position)
|
|
1189
1319
|
print("Editor closed. Starting interactive chat session...")
|
|
1190
|
-
start_chat_session(initial_chat_prompt)
|
|
1320
|
+
start_chat_session(initial_chat_prompt)
|
|
1191
1321
|
else:
|
|
1192
|
-
# Original non-interactive behavior
|
|
1193
1322
|
if args.task:
|
|
1194
1323
|
task_description = args.task
|
|
1195
|
-
# Insert task description before "## Task Instructions"
|
|
1196
1324
|
task_marker = "## Task Instructions\n\n"
|
|
1197
1325
|
insertion_point = prompt_template.find(task_marker)
|
|
1198
1326
|
if insertion_point != -1:
|
|
1199
1327
|
final_prompt = prompt_template[:insertion_point + len(task_marker)] + task_description + "\n\n" + prompt_template[insertion_point + len(task_marker):]
|
|
1200
|
-
else:
|
|
1328
|
+
else:
|
|
1201
1329
|
final_prompt = prompt_template[:cursor_position] + task_description + prompt_template[cursor_position:]
|
|
1202
1330
|
print("\nUsing task description from -t argument.")
|
|
1203
1331
|
else:
|
|
@@ -1209,7 +1337,6 @@ def main():
|
|
|
1209
1337
|
print(final_prompt)
|
|
1210
1338
|
print("-" * 80)
|
|
1211
1339
|
|
|
1212
|
-
# Copy the prompt to clipboard
|
|
1213
1340
|
try:
|
|
1214
1341
|
pyperclip.copy(final_prompt)
|
|
1215
1342
|
separator = "\n" + "=" * 40 + "\n☕🍝 Kopipasta Complete! 🍝☕\n" + "=" * 40 + "\n"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
kopipasta/import_parser.py,sha256=yLzkMlQm2avKjfqcpMY0PxbA_2ihV9gSYJplreWIPEQ,12424
|
|
3
|
+
kopipasta/main.py,sha256=gTIPH5dScVKym_5QFrWE3gmChvrFaIgMJyvnTBxc_iI,62607
|
|
4
|
+
kopipasta-0.27.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
|
|
5
|
+
kopipasta-0.27.0.dist-info/METADATA,sha256=ubZEEhK8410J4kZ5x1MTAWfm2EXPO4NtLqbpUB44rcM,8610
|
|
6
|
+
kopipasta-0.27.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
7
|
+
kopipasta-0.27.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
|
|
8
|
+
kopipasta-0.27.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
|
|
9
|
+
kopipasta-0.27.0.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
kopipasta/import_parser.py,sha256=Y1YzoEXW34caqCcy-yUXTzw44YbY1SjLJbZubjagDSs,12454
|
|
3
|
-
kopipasta/main.py,sha256=-hDEhSR1wFuAKDOypXc7OfR4fOdBWb37Ux5mIhzsZuM,54527
|
|
4
|
-
kopipasta-0.26.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
|
|
5
|
-
kopipasta-0.26.0.dist-info/METADATA,sha256=3WGVQqrL3aeNzzlOrziYBPbu-kRWPDKza-NKVi7GTFI,8610
|
|
6
|
-
kopipasta-0.26.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
7
|
-
kopipasta-0.26.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
|
|
8
|
-
kopipasta-0.26.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
|
|
9
|
-
kopipasta-0.26.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|