pdd-cli 0.0.90__py3-none-any.whl → 0.0.121__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.
- pdd/__init__.py +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +506 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +537 -0
- pdd/agentic_common.py +533 -770
- pdd/agentic_crash.py +2 -1
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +582 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +27 -9
- pdd/agentic_verify.py +3 -2
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +236 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +113 -48
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +358 -0
- pdd/commands/fix.py +155 -114
- pdd/commands/generate.py +5 -0
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +225 -163
- pdd/commands/sessions.py +284 -0
- pdd/commands/utility.py +12 -7
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +44 -7
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +68 -20
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +208 -6
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +531 -97
- pdd/load_prompt_template.py +15 -34
- pdd/operation_log.py +342 -0
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +122 -97
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +140 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +19 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1347 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +217 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +289 -211
- pdd/sync_order.py +304 -0
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +68 -26
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
- pdd_cli-0.0.121.dist-info/RECORD +229 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
pdd/summarize_directory.py
CHANGED
|
@@ -1,265 +1,264 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
try:
|
|
4
|
-
from datetime import UTC
|
|
5
|
-
except ImportError:
|
|
6
|
-
# Python < 3.11 compatibility
|
|
7
|
-
from datetime import timezone
|
|
8
|
-
UTC = timezone.utc
|
|
9
|
-
from io import StringIO
|
|
10
|
-
import os
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
11
3
|
import glob
|
|
4
|
+
import hashlib
|
|
5
|
+
import io
|
|
12
6
|
import csv
|
|
13
|
-
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional, List, Dict, Tuple, Callable
|
|
14
9
|
from pydantic import BaseModel, Field
|
|
15
|
-
from rich import
|
|
16
|
-
from rich.progress import
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
# Internal imports based on package structure
|
|
19
14
|
from .llm_invoke import llm_invoke
|
|
15
|
+
from .load_prompt_template import load_prompt_template
|
|
20
16
|
from . import DEFAULT_TIME
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
file_summary: str = Field(description="The summary of the file")
|
|
18
|
+
console = Console()
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
if not csv_content or csv_content.isspace():
|
|
29
|
-
return False
|
|
30
|
-
reader = csv.DictReader(StringIO(csv_content.lstrip()))
|
|
31
|
-
if not reader.fieldnames:
|
|
32
|
-
return False
|
|
33
|
-
required_columns = {'full_path', 'file_summary', 'date'}
|
|
34
|
-
if not all(col in reader.fieldnames for col in required_columns):
|
|
35
|
-
return False
|
|
36
|
-
try:
|
|
37
|
-
first_row = next(reader, None)
|
|
38
|
-
if not first_row:
|
|
39
|
-
return True
|
|
40
|
-
return all(key in first_row for key in required_columns)
|
|
41
|
-
except csv.Error:
|
|
42
|
-
return False
|
|
43
|
-
except Exception:
|
|
44
|
-
return False
|
|
45
|
-
|
|
46
|
-
def normalize_path(path: str) -> str:
|
|
47
|
-
"""Normalize path for consistent comparison."""
|
|
48
|
-
return os.path.normpath(path.strip().strip('"').strip())
|
|
49
|
-
|
|
50
|
-
def parse_date(date_str: str) -> datetime:
|
|
51
|
-
"""Parse date string to datetime with proper error handling."""
|
|
52
|
-
try:
|
|
53
|
-
dt = datetime.fromisoformat(date_str.strip())
|
|
54
|
-
return dt if dt.tzinfo else dt.replace(tzinfo=UTC)
|
|
55
|
-
except Exception:
|
|
56
|
-
return datetime.now(UTC)
|
|
57
|
-
|
|
58
|
-
def parse_existing_csv(csv_content: str, verbose: bool = False) -> dict:
|
|
59
|
-
"""Parse existing CSV file and return normalized data."""
|
|
60
|
-
existing_data = {}
|
|
61
|
-
try:
|
|
62
|
-
# Clean the CSV content by removing leading/trailing whitespace from each line
|
|
63
|
-
cleaned_lines = [line.strip() for line in csv_content.splitlines()]
|
|
64
|
-
cleaned_content = '\n'.join(cleaned_lines)
|
|
65
|
-
|
|
66
|
-
reader = csv.DictReader(StringIO(cleaned_content))
|
|
67
|
-
for row in reader:
|
|
68
|
-
try:
|
|
69
|
-
normalized_path = normalize_path(row['full_path'])
|
|
70
|
-
existing_data[normalized_path] = {
|
|
71
|
-
'file_summary': row['file_summary'].strip().strip('"'),
|
|
72
|
-
'date': row['date'].strip()
|
|
73
|
-
}
|
|
74
|
-
if verbose:
|
|
75
|
-
print(f"[green]Parsed existing entry for: {normalized_path}[/green]")
|
|
76
|
-
except Exception as e:
|
|
77
|
-
if verbose:
|
|
78
|
-
print(f"[yellow]Warning: Skipping invalid CSV row: {str(e)}[/yellow]")
|
|
79
|
-
except Exception as e:
|
|
80
|
-
if verbose:
|
|
81
|
-
print(f"[yellow]Warning: Error parsing CSV: {str(e)}[/yellow]")
|
|
82
|
-
raise ValueError("Invalid CSV file format.")
|
|
83
|
-
return existing_data
|
|
20
|
+
class FileSummary(BaseModel):
|
|
21
|
+
"""Pydantic model for structured LLM output."""
|
|
22
|
+
file_summary: str = Field(..., description="A concise summary of the file contents.")
|
|
84
23
|
|
|
85
24
|
def summarize_directory(
|
|
86
25
|
directory_path: str,
|
|
87
26
|
strength: float,
|
|
88
27
|
temperature: float,
|
|
89
|
-
verbose: bool,
|
|
90
28
|
time: float = DEFAULT_TIME,
|
|
29
|
+
verbose: bool = False,
|
|
91
30
|
csv_file: Optional[str] = None,
|
|
92
31
|
progress_callback: Optional[Callable[[int, int], None]] = None
|
|
93
32
|
) -> Tuple[str, float, str]:
|
|
94
33
|
"""
|
|
95
|
-
|
|
34
|
+
Summarizes files in a directory using an LLM, with caching based on content hashes.
|
|
96
35
|
|
|
97
|
-
|
|
98
|
-
directory_path
|
|
99
|
-
strength
|
|
100
|
-
temperature
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
csv_file
|
|
104
|
-
progress_callback
|
|
105
|
-
Called with (current, total) for each file processed. Used by TUI ProgressBar.
|
|
36
|
+
Args:
|
|
37
|
+
directory_path: Path to the directory/files (supports wildcards, e.g., 'src/*.py').
|
|
38
|
+
strength: Float (0-1) indicating LLM model strength.
|
|
39
|
+
temperature: Float controlling LLM randomness.
|
|
40
|
+
time: Float (0-1) controlling thinking effort.
|
|
41
|
+
verbose: Whether to print detailed logs.
|
|
42
|
+
csv_file: Existing CSV content string to check for cache hits.
|
|
43
|
+
progress_callback: Optional callback for progress updates (current, total).
|
|
106
44
|
|
|
107
45
|
Returns:
|
|
108
|
-
Tuple
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
46
|
+
Tuple containing:
|
|
47
|
+
- csv_output (str): The updated CSV content.
|
|
48
|
+
- total_cost (float): Total cost of LLM operations.
|
|
49
|
+
- model_name (str): Name of the model used (from the last successful call).
|
|
112
50
|
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
csv_output = "full_path,file_summary,date\n"
|
|
128
|
-
total_cost = 0.0
|
|
129
|
-
model_name = "None"
|
|
130
|
-
|
|
131
|
-
existing_data = {}
|
|
132
|
-
if csv_file:
|
|
133
|
-
if not validate_csv_format(csv_file):
|
|
134
|
-
raise ValueError("Invalid CSV file format.")
|
|
135
|
-
existing_data = parse_existing_csv(csv_file, verbose)
|
|
136
|
-
|
|
137
|
-
# Expand directory_path: support plain directories or glob patterns
|
|
51
|
+
|
|
52
|
+
# Step 1: Input Validation
|
|
53
|
+
if not isinstance(directory_path, str) or not directory_path:
|
|
54
|
+
raise ValueError("Invalid 'directory_path'.")
|
|
55
|
+
if not (0.0 <= strength <= 1.0):
|
|
56
|
+
raise ValueError("Invalid 'strength' value.")
|
|
57
|
+
if not (isinstance(temperature, (int, float)) and temperature >= 0):
|
|
58
|
+
raise ValueError("Invalid 'temperature' value.")
|
|
59
|
+
if not isinstance(verbose, bool):
|
|
60
|
+
raise ValueError("Invalid 'verbose' value.")
|
|
61
|
+
|
|
62
|
+
# Parse existing CSV if provided to validate format and get cached entries
|
|
63
|
+
existing_data: Dict[str, Dict[str, str]] = {}
|
|
64
|
+
if csv_file:
|
|
138
65
|
try:
|
|
139
|
-
|
|
66
|
+
f = io.StringIO(csv_file)
|
|
67
|
+
reader = csv.DictReader(f)
|
|
68
|
+
if reader.fieldnames and not all(field in reader.fieldnames for field in ['full_path', 'file_summary', 'content_hash']):
|
|
69
|
+
raise ValueError("Missing required columns.")
|
|
70
|
+
for row in reader:
|
|
71
|
+
if 'full_path' in row and 'content_hash' in row:
|
|
72
|
+
# Use normalized path for cache key consistency
|
|
73
|
+
existing_data[os.path.normpath(row['full_path'])] = row
|
|
140
74
|
except Exception:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
75
|
+
raise ValueError("Invalid CSV file format.")
|
|
76
|
+
|
|
77
|
+
# Step 2: Load prompt template
|
|
78
|
+
prompt_template_name = "summarize_file_LLM"
|
|
79
|
+
prompt_template = load_prompt_template(prompt_template_name)
|
|
80
|
+
if not prompt_template:
|
|
81
|
+
raise FileNotFoundError(f"Prompt template '{prompt_template_name}' is empty or missing.")
|
|
82
|
+
|
|
83
|
+
# Step 3: Get list of files matching directory_path
|
|
84
|
+
# If directory_path is a directory, convert to recursive glob pattern
|
|
85
|
+
if os.path.isdir(directory_path):
|
|
86
|
+
search_pattern = os.path.join(directory_path, "**", "*")
|
|
87
|
+
else:
|
|
88
|
+
search_pattern = directory_path
|
|
89
|
+
|
|
90
|
+
files = glob.glob(search_pattern, recursive=True)
|
|
91
|
+
|
|
92
|
+
# Filter out directories, keep only files
|
|
93
|
+
# Also filter out __pycache__ and .pyc/.pyo files
|
|
94
|
+
filtered_files = []
|
|
95
|
+
for f in files:
|
|
96
|
+
if os.path.isfile(f):
|
|
97
|
+
if "__pycache__" in f:
|
|
98
|
+
continue
|
|
99
|
+
if f.endswith(('.pyc', '.pyo')):
|
|
100
|
+
continue
|
|
101
|
+
filtered_files.append(f)
|
|
102
|
+
|
|
103
|
+
files = filtered_files
|
|
104
|
+
|
|
105
|
+
# Step 4: Return early if no files
|
|
106
|
+
if not files:
|
|
107
|
+
# Return empty CSV header
|
|
108
|
+
output_io = io.StringIO()
|
|
109
|
+
writer = csv.DictWriter(output_io, fieldnames=['full_path', 'file_summary', 'content_hash'])
|
|
110
|
+
writer.writeheader()
|
|
111
|
+
return output_io.getvalue(), 0.0, "None"
|
|
112
|
+
|
|
113
|
+
results_data: List[Dict[str, str]] = []
|
|
114
|
+
total_cost = 0.0
|
|
115
|
+
last_model_name = "cached"
|
|
116
|
+
|
|
117
|
+
# Step 6: Iterate through files with progress reporting
|
|
118
|
+
total_files = len(files)
|
|
119
|
+
|
|
120
|
+
if progress_callback:
|
|
121
|
+
for i, file_path in enumerate(files):
|
|
122
|
+
progress_callback(i + 1, total_files)
|
|
123
|
+
cost, model = _process_single_file_logic(
|
|
124
|
+
file_path,
|
|
125
|
+
existing_data,
|
|
126
|
+
prompt_template,
|
|
127
|
+
strength,
|
|
128
|
+
temperature,
|
|
129
|
+
time,
|
|
130
|
+
verbose,
|
|
131
|
+
results_data
|
|
132
|
+
)
|
|
133
|
+
total_cost += cost
|
|
134
|
+
if model != "cached":
|
|
135
|
+
last_model_name = model
|
|
136
|
+
else:
|
|
137
|
+
console.print(f"[bold blue]Summarizing {len(files)} files in '{directory_path}'...[/bold blue]")
|
|
138
|
+
with Progress(
|
|
139
|
+
SpinnerColumn(),
|
|
140
|
+
TextColumn("[progress.description]{task.description}"),
|
|
141
|
+
BarColumn(),
|
|
142
|
+
TextColumn("{task.percentage:>3.0f}%"),
|
|
143
|
+
console=console
|
|
144
|
+
) as progress:
|
|
145
|
+
task = progress.add_task("[cyan]Processing files...", total=len(files))
|
|
146
|
+
for file_path in files:
|
|
147
|
+
cost, model = _process_single_file_logic(
|
|
148
|
+
file_path,
|
|
149
|
+
existing_data,
|
|
150
|
+
prompt_template,
|
|
151
|
+
strength,
|
|
152
|
+
temperature,
|
|
153
|
+
time,
|
|
154
|
+
verbose,
|
|
155
|
+
results_data
|
|
156
|
+
)
|
|
157
|
+
total_cost += cost
|
|
158
|
+
if model != "cached":
|
|
159
|
+
last_model_name = model
|
|
160
|
+
progress.advance(task)
|
|
161
|
+
|
|
162
|
+
# Step 7: Generate CSV output
|
|
163
|
+
output_io = io.StringIO()
|
|
164
|
+
fieldnames = ['full_path', 'file_summary', 'content_hash']
|
|
165
|
+
writer = csv.DictWriter(output_io, fieldnames=fieldnames)
|
|
166
|
+
|
|
167
|
+
writer.writeheader()
|
|
168
|
+
writer.writerows(results_data)
|
|
169
|
+
|
|
170
|
+
csv_output = output_io.getvalue()
|
|
171
|
+
|
|
172
|
+
return csv_output, total_cost, last_model_name
|
|
173
|
+
|
|
174
|
+
def _process_single_file_logic(
|
|
175
|
+
file_path: str,
|
|
176
|
+
existing_data: Dict[str, Dict[str, str]],
|
|
177
|
+
prompt_template: str,
|
|
178
|
+
strength: float,
|
|
179
|
+
temperature: float,
|
|
180
|
+
time: float,
|
|
181
|
+
verbose: bool,
|
|
182
|
+
results_data: List[Dict[str, str]]
|
|
183
|
+
) -> Tuple[float, str]:
|
|
184
|
+
"""
|
|
185
|
+
Helper function to process a single file: read, hash, check cache, summarize if needed.
|
|
186
|
+
Returns (cost, model_name).
|
|
187
|
+
"""
|
|
188
|
+
cost = 0.0
|
|
189
|
+
model_name = "cached"
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
# Step 6a: Read file
|
|
193
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
194
|
+
content = f.read()
|
|
195
|
+
|
|
196
|
+
# Step 6b: Compute hash
|
|
197
|
+
current_hash = hashlib.sha256(content.encode('utf-8')).hexdigest()
|
|
198
|
+
|
|
199
|
+
summary = ""
|
|
200
|
+
|
|
201
|
+
# Step 6c: Check cache (using normalized path)
|
|
202
|
+
normalized_path = os.path.normpath(file_path)
|
|
203
|
+
cache_hit = False
|
|
204
|
+
|
|
205
|
+
if normalized_path in existing_data:
|
|
206
|
+
cached_entry = existing_data[normalized_path]
|
|
207
|
+
# Step 6d: Check hash match
|
|
208
|
+
if cached_entry.get('content_hash') == current_hash:
|
|
209
|
+
# Step 6e: Reuse summary
|
|
210
|
+
summary = cached_entry.get('file_summary', "")
|
|
211
|
+
cache_hit = True
|
|
256
212
|
if verbose:
|
|
257
|
-
print(f"[
|
|
258
|
-
date_generated = datetime.now(UTC).isoformat()
|
|
259
|
-
csv_output += f'"{relative_path}","Error processing file",{date_generated}\n'
|
|
213
|
+
console.print(f"[dim]Cache hit for {file_path}[/dim]")
|
|
260
214
|
|
|
261
|
-
|
|
215
|
+
# Step 6f: Summarize if needed
|
|
216
|
+
if not cache_hit:
|
|
217
|
+
if verbose:
|
|
218
|
+
console.print(f"[dim]Summarizing {file_path}...[/dim]")
|
|
219
|
+
|
|
220
|
+
llm_result = llm_invoke(
|
|
221
|
+
prompt=prompt_template,
|
|
222
|
+
input_json={"file_contents": content},
|
|
223
|
+
strength=strength,
|
|
224
|
+
temperature=temperature,
|
|
225
|
+
time=time,
|
|
226
|
+
output_pydantic=FileSummary,
|
|
227
|
+
verbose=verbose
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
file_summary_obj: FileSummary = llm_result['result']
|
|
231
|
+
summary = file_summary_obj.file_summary
|
|
232
|
+
|
|
233
|
+
cost = llm_result.get('cost', 0.0)
|
|
234
|
+
model_name = llm_result.get('model_name', "unknown")
|
|
235
|
+
|
|
236
|
+
# Step 6g: Store data
|
|
237
|
+
# Note: Requirement says "Store the relative path (not the full path)" in Step 6g description,
|
|
238
|
+
# but Output definition says "full_path". The existing code stored file_path (from glob).
|
|
239
|
+
# The new prompt Step 6g says "Store the relative path".
|
|
240
|
+
# However, the Output schema explicitly demands 'full_path'.
|
|
241
|
+
# To satisfy the Output schema which is usually the contract, we keep using file_path as 'full_path'.
|
|
242
|
+
# But we will calculate relative path if needed.
|
|
243
|
+
# Given the conflict, usually the Output definition takes precedence for the CSV column name,
|
|
244
|
+
# but the value might need to be relative.
|
|
245
|
+
# Let's stick to the existing behavior (glob path) which satisfied 'full_path' previously,
|
|
246
|
+
# unless 'relative path' implies os.path.relpath(file_path, start=directory_path_root).
|
|
247
|
+
# The prompt is slightly ambiguous: "Store the relative path... in the current data dictionary" vs Output "full_path".
|
|
248
|
+
# We will store the path as found by glob to ensure it matches the 'full_path' column expectation.
|
|
249
|
+
|
|
250
|
+
results_data.append({
|
|
251
|
+
'full_path': file_path,
|
|
252
|
+
'file_summary': summary,
|
|
253
|
+
'content_hash': current_hash
|
|
254
|
+
})
|
|
262
255
|
|
|
263
256
|
except Exception as e:
|
|
264
|
-
print(f"[red]
|
|
265
|
-
|
|
257
|
+
console.print(f"[bold red]Error processing file {file_path}:[/bold red] {e}")
|
|
258
|
+
results_data.append({
|
|
259
|
+
'full_path': file_path,
|
|
260
|
+
'file_summary': f"Error processing file: {str(e)}",
|
|
261
|
+
'content_hash': "error"
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return cost, model_name
|
pdd/sync_animation.py
CHANGED
|
@@ -336,8 +336,13 @@ def _draw_connecting_lines_and_arrows(state: AnimationState, console_width: int)
|
|
|
336
336
|
max_x = max(all_branch_xs)
|
|
337
337
|
|
|
338
338
|
# Draw horizontal line on line 2 (index 2)
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
# Clamp drawing range to console width to prevent IndexError and wrapping
|
|
340
|
+
draw_start = max(min_x, 0)
|
|
341
|
+
draw_end = min(max_x, console_width - 1)
|
|
342
|
+
|
|
343
|
+
if draw_start <= draw_end:
|
|
344
|
+
for i in range(draw_start, draw_end + 1):
|
|
345
|
+
line_parts[2][i] = "─"
|
|
341
346
|
|
|
342
347
|
# Draw vertical connectors only where needed
|
|
343
348
|
# Prompt always connects vertically (lines 0,1 above junction, lines 3,4,5 below)
|
|
@@ -639,5 +644,4 @@ def sync_animation(
|
|
|
639
644
|
console.print_exception(show_locals=True)
|
|
640
645
|
print(f"Error in animation: {e}", flush=True)
|
|
641
646
|
finally:
|
|
642
|
-
_final_logo_animation_sequence(console)
|
|
643
|
-
|
|
647
|
+
_final_logo_animation_sequence(console)
|