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.
Files changed (151) hide show
  1. pdd/__init__.py +38 -6
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +506 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +537 -0
  6. pdd/agentic_common.py +533 -770
  7. pdd/agentic_crash.py +2 -1
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +582 -0
  10. pdd/agentic_fix.py +118 -3
  11. pdd/agentic_update.py +27 -9
  12. pdd/agentic_verify.py +3 -2
  13. pdd/architecture_sync.py +565 -0
  14. pdd/auth_service.py +210 -0
  15. pdd/auto_deps_main.py +63 -53
  16. pdd/auto_include.py +236 -3
  17. pdd/auto_update.py +125 -47
  18. pdd/bug_main.py +195 -23
  19. pdd/cmd_test_main.py +345 -197
  20. pdd/code_generator.py +4 -2
  21. pdd/code_generator_main.py +118 -32
  22. pdd/commands/__init__.py +6 -0
  23. pdd/commands/analysis.py +113 -48
  24. pdd/commands/auth.py +309 -0
  25. pdd/commands/connect.py +358 -0
  26. pdd/commands/fix.py +155 -114
  27. pdd/commands/generate.py +5 -0
  28. pdd/commands/maintenance.py +3 -2
  29. pdd/commands/misc.py +8 -0
  30. pdd/commands/modify.py +225 -163
  31. pdd/commands/sessions.py +284 -0
  32. pdd/commands/utility.py +12 -7
  33. pdd/construct_paths.py +334 -32
  34. pdd/context_generator_main.py +167 -170
  35. pdd/continue_generation.py +6 -3
  36. pdd/core/__init__.py +33 -0
  37. pdd/core/cli.py +44 -7
  38. pdd/core/cloud.py +237 -0
  39. pdd/core/dump.py +68 -20
  40. pdd/core/errors.py +4 -0
  41. pdd/core/remote_session.py +61 -0
  42. pdd/crash_main.py +219 -23
  43. pdd/data/llm_model.csv +4 -4
  44. pdd/docs/prompting_guide.md +864 -0
  45. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  46. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  47. pdd/fix_code_loop.py +208 -34
  48. pdd/fix_code_module_errors.py +6 -2
  49. pdd/fix_error_loop.py +291 -38
  50. pdd/fix_main.py +208 -6
  51. pdd/fix_verification_errors_loop.py +235 -26
  52. pdd/fix_verification_main.py +269 -83
  53. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  54. pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
  55. pdd/frontend/dist/index.html +376 -0
  56. pdd/frontend/dist/logo.svg +33 -0
  57. pdd/generate_output_paths.py +46 -5
  58. pdd/generate_test.py +212 -151
  59. pdd/get_comment.py +19 -44
  60. pdd/get_extension.py +8 -9
  61. pdd/get_jwt_token.py +309 -20
  62. pdd/get_language.py +8 -7
  63. pdd/get_run_command.py +7 -5
  64. pdd/insert_includes.py +2 -1
  65. pdd/llm_invoke.py +531 -97
  66. pdd/load_prompt_template.py +15 -34
  67. pdd/operation_log.py +342 -0
  68. pdd/path_resolution.py +140 -0
  69. pdd/postprocess.py +122 -97
  70. pdd/preprocess.py +68 -12
  71. pdd/preprocess_main.py +33 -1
  72. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  73. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  74. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  75. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  76. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  77. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  78. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  79. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  80. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  81. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  82. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  83. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  84. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +140 -0
  85. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  86. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  87. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  88. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  89. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  90. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  91. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  92. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  93. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  94. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  95. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  96. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  97. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  98. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  99. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  100. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  101. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  102. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  103. pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
  104. pdd/prompts/agentic_update_LLM.prompt +192 -338
  105. pdd/prompts/auto_include_LLM.prompt +22 -0
  106. pdd/prompts/change_LLM.prompt +3093 -1
  107. pdd/prompts/detect_change_LLM.prompt +571 -14
  108. pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
  109. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
  110. pdd/prompts/generate_test_LLM.prompt +19 -1
  111. pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
  112. pdd/prompts/insert_includes_LLM.prompt +262 -252
  113. pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
  114. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  115. pdd/remote_session.py +876 -0
  116. pdd/server/__init__.py +52 -0
  117. pdd/server/app.py +335 -0
  118. pdd/server/click_executor.py +587 -0
  119. pdd/server/executor.py +338 -0
  120. pdd/server/jobs.py +661 -0
  121. pdd/server/models.py +241 -0
  122. pdd/server/routes/__init__.py +31 -0
  123. pdd/server/routes/architecture.py +451 -0
  124. pdd/server/routes/auth.py +364 -0
  125. pdd/server/routes/commands.py +929 -0
  126. pdd/server/routes/config.py +42 -0
  127. pdd/server/routes/files.py +603 -0
  128. pdd/server/routes/prompts.py +1347 -0
  129. pdd/server/routes/websocket.py +473 -0
  130. pdd/server/security.py +243 -0
  131. pdd/server/terminal_spawner.py +217 -0
  132. pdd/server/token_counter.py +222 -0
  133. pdd/summarize_directory.py +236 -237
  134. pdd/sync_animation.py +8 -4
  135. pdd/sync_determine_operation.py +329 -47
  136. pdd/sync_main.py +272 -28
  137. pdd/sync_orchestration.py +289 -211
  138. pdd/sync_order.py +304 -0
  139. pdd/template_expander.py +161 -0
  140. pdd/templates/architecture/architecture_json.prompt +41 -46
  141. pdd/trace.py +1 -1
  142. pdd/track_cost.py +0 -13
  143. pdd/unfinished_prompt.py +2 -1
  144. pdd/update_main.py +68 -26
  145. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
  146. pdd_cli-0.0.121.dist-info/RECORD +229 -0
  147. pdd_cli-0.0.90.dist-info/RECORD +0 -153
  148. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
  149. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
  150. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
  151. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
pdd/commands/modify.py CHANGED
@@ -1,32 +1,29 @@
1
- """
2
- Modify commands (change, split, update).
3
- """
4
- import click
1
+ from __future__ import annotations
2
+
3
+ import sys
5
4
  from pathlib import Path
6
- from typing import Dict, Optional, Tuple, Union
5
+ from typing import Optional, Tuple, Any
6
+
7
+ import click
8
+ from rich.console import Console
7
9
 
10
+ # Relative imports from parent package
8
11
  from ..split_main import split_main
9
12
  from ..change_main import change_main
13
+ from ..agentic_change import run_agentic_change
10
14
  from ..update_main import update_main
11
15
  from ..track_cost import track_cost
12
16
  from ..core.errors import handle_error
17
+ from ..operation_log import log_operation
13
18
 
14
- @click.command("split")
15
- @click.argument("input_prompt", type=click.Path(exists=True, dir_okay=False))
16
- @click.argument("input_code", type=click.Path(exists=True, dir_okay=False))
17
- @click.argument("example_code", type=click.Path(exists=True, dir_okay=False))
18
- @click.option(
19
- "--output-sub",
20
- type=click.Path(writable=True),
21
- default=None,
22
- help="Specify where to save the generated sub-prompt file (file or directory).",
23
- )
24
- @click.option(
25
- "--output-modified",
26
- type=click.Path(writable=True),
27
- default=None,
28
- help="Specify where to save the modified prompt file (file or directory).",
29
- )
19
+ console = Console()
20
+
21
+ @click.command()
22
+ @click.argument("input_prompt", type=click.Path(exists=True))
23
+ @click.argument("input_code", type=click.Path(exists=True))
24
+ @click.argument("example_code", type=click.Path(exists=True))
25
+ @click.option("--output-sub", help="Optional path for saving the sub-prompt.")
26
+ @click.option("--output-modified", help="Optional path for saving the modified prompt.")
30
27
  @click.pass_context
31
28
  @track_cost
32
29
  def split(
@@ -36,13 +33,15 @@ def split(
36
33
  example_code: str,
37
34
  output_sub: Optional[str],
38
35
  output_modified: Optional[str],
39
- ) -> Optional[Tuple[Dict[str, str], float, str]]:
40
- """Split large complex prompt files into smaller ones."""
41
- quiet = ctx.obj.get("quiet", False)
42
- command_name = "split"
36
+ ) -> Optional[Tuple[Any, float, str]]:
37
+ """
38
+ Split large complex prompt files into smaller, more manageable prompt files.
39
+ """
40
+ ctx.ensure_object(dict)
43
41
  try:
42
+ # Call split_main with required arguments
44
43
  result_data, total_cost, model_name = split_main(
45
- ctx=ctx,
44
+ ctx,
46
45
  input_prompt_file=input_prompt,
47
46
  input_code_file=input_code,
48
47
  example_code_file=example_code,
@@ -50,181 +49,244 @@ def split(
50
49
  output_modified=output_modified,
51
50
  )
52
51
  return result_data, total_cost, model_name
52
+
53
53
  except click.Abort:
54
54
  raise
55
55
  except Exception as e:
56
- handle_error(e, command_name, quiet)
56
+ handle_error(e, "split", ctx.obj.get("quiet", False))
57
57
  return None
58
58
 
59
59
 
60
- @click.command("change")
61
- @click.argument("change_prompt_file", type=click.Path(exists=True, dir_okay=False))
62
- @click.argument("input_code", type=click.Path(exists=True)) # Can be file or dir
63
- @click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False), required=False)
64
- @click.option(
65
- "--budget",
66
- type=float,
67
- default=5.0,
68
- show_default=True,
69
- help="Maximum cost allowed for the change process.",
70
- )
71
- @click.option(
72
- "--output",
73
- type=click.Path(writable=True),
74
- default=None,
75
- help="Specify where to save the modified prompt file (file or directory).",
76
- )
77
- @click.option(
78
- "--csv",
79
- "use_csv",
80
- is_flag=True,
81
- default=False,
82
- help="Use a CSV file for batch change prompts.",
83
- )
60
+ @click.command()
61
+ @click.argument("args", nargs=-1)
62
+ @click.option("--manual", is_flag=True, default=False, help="Use legacy manual mode.")
63
+ @click.option("--budget", type=float, default=5.0, help="Budget for the operation.")
64
+ @click.option("--output", help="Output path.")
65
+ @click.option("--csv", is_flag=True, help="Use CSV input for batch processing.")
66
+ @click.option("--timeout-adder", type=float, default=0.0, help="Additional seconds to add to each step's timeout (agentic mode only).")
67
+ @click.option("--no-github-state", is_flag=True, default=False, help="Disable GitHub state persistence (agentic mode only).")
84
68
  @click.pass_context
85
69
  @track_cost
86
70
  def change(
87
71
  ctx: click.Context,
88
- change_prompt_file: str,
89
- input_code: str,
90
- input_prompt_file: Optional[str],
91
- output: Optional[str],
92
- use_csv: bool,
72
+ args: Tuple[str, ...],
73
+ manual: bool,
93
74
  budget: float,
94
- ) -> Optional[Tuple[Union[str, Dict], float, str]]:
95
- """Modify prompt(s) based on change instructions."""
96
- quiet = ctx.obj.get("quiet", False)
97
- command_name = "change"
75
+ output: Optional[str],
76
+ csv: bool,
77
+ timeout_adder: float,
78
+ no_github_state: bool,
79
+ ) -> Optional[Tuple[Any, float, str]]:
80
+ """
81
+ Modify an input prompt file based on a change prompt or issue.
82
+
83
+ Agentic Mode (default):
84
+ pdd change ISSUE_URL
85
+
86
+ Manual Mode (--manual):
87
+ pdd change --manual CHANGE_PROMPT_FILE INPUT_CODE_FILE [INPUT_PROMPT_FILE]
88
+ """
89
+ ctx.ensure_object(dict)
90
+
98
91
  try:
99
- # --- ADD VALIDATION LOGIC HERE ---
100
- input_code_path = Path(input_code) # Convert to Path object
101
- if use_csv:
102
- if not input_code_path.is_dir():
103
- raise click.UsageError("INPUT_CODE must be a directory when using --csv.")
104
- if input_prompt_file:
105
- raise click.UsageError("Cannot use --csv and specify an INPUT_PROMPT_FILE simultaneously.")
106
- else: # Not using CSV
107
- if not input_prompt_file:
108
- # This check might be better inside change_main, but can be here too
109
- raise click.UsageError("INPUT_PROMPT_FILE is required when not using --csv.")
110
- if not input_code_path.is_file():
111
- # This check might be better inside change_main, but can be here too
112
- raise click.UsageError("INPUT_CODE must be a file when not using --csv.")
113
- # --- END VALIDATION LOGIC ---
114
-
115
- result_data, total_cost, model_name = change_main(
116
- ctx=ctx,
117
- change_prompt_file=change_prompt_file,
118
- input_code=input_code,
119
- input_prompt_file=input_prompt_file,
120
- output=output,
121
- use_csv=use_csv,
122
- budget=budget,
123
- )
124
- return result_data, total_cost, model_name
92
+ # Set budget in context for manual mode usage
93
+ ctx.obj["budget"] = budget
94
+
95
+ quiet = ctx.obj.get("quiet", False)
96
+ verbose = ctx.obj.get("verbose", False)
97
+
98
+ if manual:
99
+ # Manual Mode Validation and Execution
100
+ if csv:
101
+ # CSV Mode: Expecting CSV_FILE and CODE_DIRECTORY (no input_prompt)
102
+ if len(args) == 3:
103
+ raise click.UsageError("Cannot use --csv and specify an INPUT_PROMPT_FILE simultaneously.")
104
+ if len(args) != 2:
105
+ raise click.UsageError("CSV mode requires 2 arguments: CSV_FILE CODE_DIRECTORY")
106
+
107
+ change_file, input_code = args
108
+ input_prompt = None
109
+
110
+ # CSV mode requires input_code to be a directory
111
+ if not Path(input_code).is_dir():
112
+ raise click.UsageError("INPUT_CODE must be a directory when using --csv")
113
+ else:
114
+ # Standard Manual Mode: Expecting 2 or 3 arguments
115
+ if len(args) == 3:
116
+ change_file, input_code, input_prompt = args
117
+ # Non-CSV mode requires input_code to be a file, not a directory
118
+ if Path(input_code).is_dir():
119
+ raise click.UsageError("INPUT_CODE must be a file when not using --csv")
120
+ elif len(args) == 2:
121
+ change_file, input_code = args
122
+ input_prompt = None
123
+ # Without CSV mode, input_prompt_file is required
124
+ raise click.UsageError("INPUT_PROMPT_FILE is required when not using --csv")
125
+ else:
126
+ raise click.UsageError(
127
+ "Manual mode requires 3 arguments: CHANGE_PROMPT INPUT_CODE INPUT_PROMPT"
128
+ )
129
+
130
+ # Validate file existence
131
+ if not Path(change_file).exists():
132
+ raise click.UsageError(f"Change file not found: {change_file}")
133
+ if not Path(input_code).exists():
134
+ raise click.UsageError(f"Input code path not found: {input_code}")
135
+ if input_prompt and not Path(input_prompt).exists():
136
+ raise click.UsageError(f"Input prompt file not found: {input_prompt}")
137
+
138
+ # Call change_main
139
+ result, cost, model = change_main(
140
+ ctx=ctx,
141
+ change_prompt_file=change_file,
142
+ input_code=input_code,
143
+ input_prompt_file=input_prompt,
144
+ output=output,
145
+ use_csv=csv,
146
+ budget=budget
147
+ )
148
+ return result, cost, model
149
+
150
+ else:
151
+ # Agentic Mode Validation and Execution
152
+ if len(args) != 1:
153
+ raise click.UsageError("Agentic mode requires exactly 1 argument: ISSUE_URL")
154
+
155
+ issue_url = args[0]
156
+
157
+ # Call run_agentic_change
158
+ success, message, cost, model, changed_files = run_agentic_change(
159
+ issue_url=issue_url,
160
+ verbose=verbose,
161
+ quiet=quiet,
162
+ timeout_adder=timeout_adder,
163
+ use_github_state=not no_github_state
164
+ )
165
+
166
+ # Display results using click.echo as requested
167
+ if not quiet:
168
+ status = "Success" if success else "Failed"
169
+ click.echo(f"Status: {status}")
170
+ click.echo(f"Message: {message}")
171
+ click.echo(f"Cost: ${cost:.4f}")
172
+ click.echo(f"Model: {model}")
173
+ if changed_files:
174
+ click.echo("Changed files:")
175
+ for f in changed_files:
176
+ click.echo(f" - {f}")
177
+
178
+ return message, cost, model
179
+
125
180
  except click.Abort:
126
181
  raise
127
- except (click.UsageError, Exception) as e: # Catch specific and general exceptions
128
- handle_error(e, command_name, quiet)
182
+ except Exception as e:
183
+ handle_error(e, "change", ctx.obj.get("quiet", False))
129
184
  return None
130
185
 
131
186
 
132
- @click.command("update")
133
- @click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False), required=False)
134
- @click.argument("modified_code_file", type=click.Path(exists=True, dir_okay=False), required=False)
135
- @click.argument("input_code_file", type=click.Path(exists=True, dir_okay=False), required=False)
136
- @click.option(
137
- "--output",
138
- type=click.Path(writable=True),
139
- default=None,
140
- help="Specify where to save the updated prompt file(s). For single files: saves to this specific path or directory. For repository mode: saves all prompts to this directory. If not specified, uses the original prompt location (single file) or 'prompts' directory (repository mode).",
141
- )
142
- @click.option(
143
- "--git",
144
- "use_git",
145
- is_flag=True,
146
- default=False,
147
- help="Use git history to find the original code file.",
148
- )
149
- @click.option(
150
- "--extensions",
151
- type=str,
152
- default=None,
153
- help="Comma-separated list of file extensions to update in repo mode (e.g., 'py,js,ts').",
154
- )
155
- @click.option(
156
- "--simple",
157
- is_flag=True,
158
- default=False,
159
- help="Use legacy 2-stage LLM update instead of agentic mode.",
160
- )
187
+ @click.command()
188
+ @click.argument("files", nargs=-1)
189
+ @click.option("--extensions", help="Comma-separated extensions for repo mode.")
190
+ @click.option("--directory", help="Directory to scan for repo mode.")
191
+ @click.option("--git", is_flag=True, help="Use git history for original code.")
192
+ @click.option("--output", help="Output path for the updated prompt.")
193
+ @click.option("--simple", is_flag=True, default=False, help="Use legacy simple update.")
161
194
  @click.pass_context
195
+ @log_operation(operation="update", clears_run_report=True)
162
196
  @track_cost
163
197
  def update(
164
198
  ctx: click.Context,
165
- input_prompt_file: Optional[str],
166
- modified_code_file: Optional[str],
167
- input_code_file: Optional[str],
168
- output: Optional[str],
169
- use_git: bool,
199
+ files: Tuple[str, ...],
170
200
  extensions: Optional[str],
201
+ directory: Optional[str],
202
+ git: bool,
203
+ output: Optional[str],
171
204
  simple: bool,
172
- ) -> Optional[Tuple[str, float, str]]:
205
+ ) -> Optional[Tuple[Any, float, str]]:
173
206
  """
174
- Update prompts based on code changes.
207
+ Update the original prompt file based on code changes.
175
208
 
176
- This command operates in two modes:
177
-
178
- 1. **Single-File Mode:** When you provide at least a code file, it updates
179
- or generates a single prompt.
180
- - `pdd update <CODE_FILE>`: Generates a new prompt for the code.
181
- - `pdd update [PROMPT_FILE] <CODE_FILE>`: Updates prompt based on code.
182
- - `pdd update [PROMPT_FILE] <CODE_FILE> <ORIGINAL_CODE_FILE>`: Updates prompt using explicit original code.
183
-
184
- 2. **Repository-Wide Mode:** When you provide no file arguments, it scans the
185
- entire repository, finds all code/prompt pairs, creates missing prompts,
186
- and updates them all based on the latest git changes.
187
- - `pdd update`: Updates all prompts for modified files in the repo.
209
+ Repo-wide mode (no args): Scan entire repo.
210
+ Single-file mode (1 arg): Update prompt for specific code file.
188
211
  """
189
- quiet = ctx.obj.get("quiet", False)
190
- command_name = "update"
212
+ ctx.ensure_object(dict)
191
213
  try:
192
- # In single-file generation mode, when only one positional argument is provided,
193
- # it is treated as the code file (not the prompt file). This enables the workflow:
194
- # `pdd update <CODE_FILE>` to generate a new prompt for the given code file.
195
- # So if input_prompt_file has a value but modified_code_file is None,
196
- # we reassign input_prompt_file to actual_modified_code_file.
197
- if input_prompt_file is not None and modified_code_file is None:
198
- actual_modified_code_file = input_prompt_file
199
- actual_input_prompt_file = None
214
+ # Handle argument counts per modify_python.prompt spec (aligned with README)
215
+ if len(files) == 0:
216
+ # Repo-wide mode
217
+ is_repo_mode = True
218
+ input_prompt_file = None
219
+ modified_code_file = None
220
+ input_code_file = None
221
+ elif len(files) == 1:
222
+ # Regeneration mode: just the code file
223
+ is_repo_mode = False
224
+ input_prompt_file = None
225
+ modified_code_file = files[0]
226
+ input_code_file = None
227
+ elif len(files) == 2:
228
+ # Git-based update: prompt + modified_code (requires --git)
229
+ if not git:
230
+ raise click.UsageError(
231
+ "Two arguments require --git flag: pdd update --git <prompt> <modified_code>"
232
+ )
233
+ is_repo_mode = False
234
+ input_prompt_file = files[0]
235
+ modified_code_file = files[1]
236
+ input_code_file = None
237
+ elif len(files) == 3:
238
+ # Manual update: prompt + modified_code + original_code
239
+ if git:
240
+ raise click.UsageError(
241
+ "Cannot use --git with 3 arguments (--git and original_code are mutually exclusive)"
242
+ )
243
+ is_repo_mode = False
244
+ input_prompt_file = files[0]
245
+ modified_code_file = files[1]
246
+ input_code_file = files[2]
200
247
  else:
201
- actual_modified_code_file = modified_code_file
202
- actual_input_prompt_file = input_prompt_file
203
-
204
- is_repo_mode = actual_input_prompt_file is None and actual_modified_code_file is None
248
+ raise click.UsageError("Too many arguments. Max 3: <prompt> <modified_code> <original_code>")
205
249
 
250
+ # Validate mode-specific options
206
251
  if is_repo_mode:
207
- if any([input_code_file, use_git]):
252
+ # Repo-wide mode: --git and --output are not allowed
253
+ if git:
254
+ raise click.UsageError(
255
+ "Cannot use --git in repository-wide mode"
256
+ )
257
+ if output:
208
258
  raise click.UsageError(
209
- "Cannot use file-specific arguments or flags like --git or --input-code in repository-wide mode (when no files are provided)."
259
+ "Cannot use --output in repository-wide mode"
260
+ )
261
+ else:
262
+ # File modes: --extensions and --directory are not allowed
263
+ if extensions:
264
+ raise click.UsageError(
265
+ "--extensions can only be used in repository-wide mode"
266
+ )
267
+ if directory:
268
+ raise click.UsageError(
269
+ "--directory can only be used in repository-wide mode"
210
270
  )
211
- elif extensions:
212
- raise click.UsageError("--extensions can only be used in repository-wide mode (when no files are provided).")
213
271
 
214
- result, total_cost, model_name = update_main(
272
+ # Call update_main with correct parameters
273
+ result, cost, model = update_main(
215
274
  ctx=ctx,
216
- input_prompt_file=actual_input_prompt_file,
217
- modified_code_file=actual_modified_code_file,
275
+ input_prompt_file=input_prompt_file,
276
+ modified_code_file=modified_code_file,
218
277
  input_code_file=input_code_file,
219
278
  output=output,
220
- use_git=use_git,
279
+ use_git=git,
221
280
  repo=is_repo_mode,
222
281
  extensions=extensions,
223
- simple=simple,
282
+ directory=directory,
283
+ simple=simple
224
284
  )
225
- return result, total_cost, model_name
285
+
286
+ return result, cost, model
287
+
226
288
  except click.Abort:
227
289
  raise
228
- except (click.UsageError, Exception) as exception:
229
- handle_error(exception, command_name, quiet)
230
- return None
290
+ except Exception as e:
291
+ handle_error(e, "update", ctx.obj.get("quiet", False))
292
+ return None