pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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 +40 -8
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -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 +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- 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 +131 -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_crash_explore_LLM.prompt +49 -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_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -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 +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generic/generate_prompt
|
|
3
|
+
description: Generate a module prompt (.prompt) for any stack (backend, frontend, CLI, jobs) using project docs and context
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [template, prompt, generic]
|
|
6
|
+
language: prompt
|
|
7
|
+
output: prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt
|
|
8
|
+
variables:
|
|
9
|
+
MODULE:
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
description: Module/component basename to generate a prompt for.
|
|
13
|
+
examples: [orders, auth, users]
|
|
14
|
+
LANG_OR_FRAMEWORK:
|
|
15
|
+
required: false
|
|
16
|
+
type: string
|
|
17
|
+
description: Target language or framework suffix used in prompt naming (matches your stack conventions).
|
|
18
|
+
examples: [Python, TypeScriptReact, Go, Java, Ruby]
|
|
19
|
+
default: Python
|
|
20
|
+
LAYER:
|
|
21
|
+
required: false
|
|
22
|
+
type: string
|
|
23
|
+
description: System layer or interface type for context.
|
|
24
|
+
examples: [backend, frontend, api, graphql, cli, job, message, config, module, component, page]
|
|
25
|
+
PRD_FILE:
|
|
26
|
+
required: false
|
|
27
|
+
type: path
|
|
28
|
+
description: Product requirements document providing overall context.
|
|
29
|
+
example_paths: [PRD.md, docs/product/prd.md]
|
|
30
|
+
API_DOC_FILE:
|
|
31
|
+
required: false
|
|
32
|
+
type: path
|
|
33
|
+
description: API documentation describing endpoints and conventions.
|
|
34
|
+
example_paths: [docs/api-documentation.md, docs/api.md]
|
|
35
|
+
DB_SCHEMA_FILE:
|
|
36
|
+
required: false
|
|
37
|
+
type: path
|
|
38
|
+
description: Database schema or ERD for backend data models.
|
|
39
|
+
example_paths: [context/database-schema.md, docs/db/schema.md]
|
|
40
|
+
BACKEND_FILES_CSV:
|
|
41
|
+
required: false
|
|
42
|
+
type: path
|
|
43
|
+
description: CSV listing backend Python files/modules (for context/reference).
|
|
44
|
+
example_paths: [prompts/backend/python_architecture.csv]
|
|
45
|
+
IO_DEPENDENCIES_CSV:
|
|
46
|
+
required: false
|
|
47
|
+
type: path
|
|
48
|
+
description: CSV of function inputs/outputs and dependencies for backend modules.
|
|
49
|
+
example_paths: [prompts/backend/io_dependencies.csv]
|
|
50
|
+
ARCHITECTURE_FILE:
|
|
51
|
+
required: true
|
|
52
|
+
type: path
|
|
53
|
+
description: Architecture JSON (from architecture/architecture_json) to drive module scope, dependencies, and interface.
|
|
54
|
+
example_paths: [architecture.json]
|
|
55
|
+
TECH_STACK_FILE:
|
|
56
|
+
required: false
|
|
57
|
+
type: path
|
|
58
|
+
description: Tech stack overview (languages, frameworks, infrastructure, tools) for shaping conventions.
|
|
59
|
+
example_paths: [docs/tech_stack.md, docs/architecture/stack.md]
|
|
60
|
+
CODE_GENERATOR_PROMPT:
|
|
61
|
+
required: false
|
|
62
|
+
type: path
|
|
63
|
+
description: Reference code generator prompt to mirror style and expectations.
|
|
64
|
+
example_paths: [prompts/code_generator_python.prompt, prompts/code_generator_main_python.prompt]
|
|
65
|
+
EXISTING_PROMPTS:
|
|
66
|
+
required: false
|
|
67
|
+
type: list
|
|
68
|
+
description: Existing prompt files to use as reference (comma/newline-separated).
|
|
69
|
+
example_paths: [prompts/orders_python.prompt, prompts/auth_python.prompt]
|
|
70
|
+
DEP_EXAMPLE_EXT:
|
|
71
|
+
required: false
|
|
72
|
+
type: string
|
|
73
|
+
description: File extension for dependency examples under context/ (for non-Python stacks).
|
|
74
|
+
examples: [py, ts, tsx, go, java]
|
|
75
|
+
default: py
|
|
76
|
+
usage:
|
|
77
|
+
generate:
|
|
78
|
+
- name: Minimal (architecture only)
|
|
79
|
+
command: pdd generate --template generic/generate_prompt -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt'
|
|
80
|
+
- name: With project docs
|
|
81
|
+
command: pdd generate --template generic/generate_prompt -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt'
|
|
82
|
+
- name: With CSVs and references (backend/Python)
|
|
83
|
+
command: pdd generate --template generic/generate_prompt -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md -e BACKEND_FILES_CSV=prompts/backend/python_architecture.csv -e IO_DEPENDENCIES_CSV=prompts/backend/io_dependencies.csv -e CODE_GENERATOR_PROMPT=prompts/code_generator_python.prompt --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt'
|
|
84
|
+
- name: Frontend (TypeScriptReact) variant
|
|
85
|
+
command: pdd generate --template generic/generate_prompt -e MODULE=profile_page -e LANG_OR_FRAMEWORK=TypeScriptReact -e LAYER=frontend -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt'
|
|
86
|
+
- name: From architecture.json
|
|
87
|
+
command: pdd generate --template generic/generate_prompt -e MODULE=orders_api -e LANG_OR_FRAMEWORK=Python -e LAYER=api -e ARCHITECTURE_FILE=architecture.json --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt'
|
|
88
|
+
|
|
89
|
+
discover:
|
|
90
|
+
enabled: false
|
|
91
|
+
max_per_pattern: 5
|
|
92
|
+
max_total: 10
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
% You are an expert prompt writer and software architect for PDD. Your goal is to write a high-quality prompt that will generate the code for the ${MODULE} module/component. The prompt you create will be used to produce a detailed implementation specification in a file named ${MODULE}_${LANG_OR_FRAMEWORK}.prompt, suitable for the specified stack and layer.
|
|
96
|
+
|
|
97
|
+
IMPORTANT: Your reply MUST begin with `<prompt>` on the very first line and end with `</prompt>` on the final line. Do not include any text, whitespace, or code fences outside this block.
|
|
98
|
+
|
|
99
|
+
% Project context (architecture required, others optional):
|
|
100
|
+
<prd><include>${PRD_FILE}</include></prd>
|
|
101
|
+
<api><include>${API_DOC_FILE}</include></api>
|
|
102
|
+
<database><include>${DB_SCHEMA_FILE}</include></database>
|
|
103
|
+
<backend_files_csv><include>${BACKEND_FILES_CSV}</include></backend_files_csv>
|
|
104
|
+
<io_dependencies_csv><include>${IO_DEPENDENCIES_CSV}</include></io_dependencies_csv>
|
|
105
|
+
<architecture><include>${ARCHITECTURE_FILE}</include></architecture>
|
|
106
|
+
<tech_stack><include>${TECH_STACK_FILE}</include></tech_stack>
|
|
107
|
+
<generate_code_cli_example><include>${CODE_GENERATOR_PROMPT}</include></generate_code_cli_example>
|
|
108
|
+
|
|
109
|
+
% Existing prompt references (optional):
|
|
110
|
+
<existing_backend_prompts><include-many>${EXISTING_PROMPTS}</include-many></existing_backend_prompts>
|
|
111
|
+
|
|
112
|
+
% Do the following:
|
|
113
|
+
- Explain concisely what you are going to do (create a prompt for the ${MODULE} module/component for the specified layer and stack).
|
|
114
|
+
- Analyze any difficulties this prompt might encounter for ${MODULE} (e.g., data modeling, API or UI contracts, transactions, idempotency, auth, state management, error handling) and briefly state mitigation strategies tailored to the given LAYER and LANG_OR_FRAMEWORK.
|
|
115
|
+
- Use the ARCHITECTURE_FILE to identify the item that corresponds to this prompt by matching `filename` to `${MODULE}_${LANG_OR_FRAMEWORK}.prompt` (or best match by basename and layer). Use that item’s `reason`, `description`, `dependencies`, `interface`, and `tags` to shape the sections below.
|
|
116
|
+
- Then create the prompt content for ${MODULE} inside XML tags named prompt, ensuring conventions fit the stack and layer.
|
|
117
|
+
- Ensure the final response consists solely of the `<prompt>...</prompt>` block; nothing else (including whitespace) may appear before `<prompt>` or after `</prompt>`.
|
|
118
|
+
|
|
119
|
+
% The prompt you generate must follow this structure:
|
|
120
|
+
1) First paragraph: describe the role and responsibility of the ${MODULE} module/component within the system (consider the LAYER if provided).
|
|
121
|
+
2) A "Requirements" section with numbered points covering functionality, contracts, error handling, validation, logging, performance, and security.
|
|
122
|
+
3) A "Dependencies" section using XML include tags for each dependency (see format below).
|
|
123
|
+
4) An "Instructions" section with precise implementation guidance (clarify inputs/outputs, function/class responsibilities, edge cases, and testing notes).
|
|
124
|
+
5) A clear "Deliverable" section describing the expected code artifacts and entry points.
|
|
125
|
+
|
|
126
|
+
% Dependencies format and conventions:
|
|
127
|
+
- Represent each dependency using an XML tag with the dependency name, and put the file path inside an <include> tag, e.g.:
|
|
128
|
+
<orders_service>
|
|
129
|
+
<include>context/orders_service_example.${DEP_EXAMPLE_EXT}</include>
|
|
130
|
+
</orders_service>
|
|
131
|
+
- Prefer real example files available in the provided context (use <include-many> when listing multiple). If examples are not provided, assume dependency examples live under context/ using the pattern context/[dependency_name]_example.${DEP_EXAMPLE_EXT}.
|
|
132
|
+
- Include all necessary dependencies for the module/component (based on the provided context and references).
|
|
133
|
+
- The ARCHITECTURE_FILE lists `dependencies` referencing other prompt filenames. Convert each dependency prompt filename into a sensible dependency name (strip language suffix and `_prompt`), and map to context files with the `${DEP_EXAMPLE_EXT}` extension if present; otherwise, list the prompt filename explicitly in a "Prompt Dependencies" subsection.
|
|
134
|
+
|
|
135
|
+
% Architecture awareness (ARCHITECTURE_FILE is required):
|
|
136
|
+
- Align the "Requirements" and "Instructions" with the selected item’s `interface.type` (e.g., page, component, module, api, graphql, cli, job, message, config).
|
|
137
|
+
- For `api`, outline endpoints (method, path, auth) consistent with the architecture description; for `page`/`component`, describe route/props/data sources; for `job`, include trigger and retry policy; for `config`, list keys and sources.
|
|
138
|
+
|
|
139
|
+
% Style and quality requirements:
|
|
140
|
+
- The generated prompt must be detailed enough to yield production-ready code.
|
|
141
|
+
- Match the style and patterns of existing *_${LANG_OR_FRAMEWORK}.prompt files when present.
|
|
142
|
+
- Do not invent technologies or files; rely on the included context. If assumptions are necessary, state them explicitly and conservatively.
|
|
143
|
+
|
|
144
|
+
% Output contract:
|
|
145
|
+
- Start the output with `<prompt>` on its own line and end with `</prompt>` on its own line.
|
|
146
|
+
- Do not emit any characters (including whitespace, markdown fences, or commentary) outside the `<prompt>...</prompt>` block.
|
|
147
|
+
- Within the tags, include the sections described above as plain text.
|
|
148
|
+
- OUTPUT FORMAT (authoritative – copy/paste and replace the bracketed placeholders, keeping every literal token):
|
|
149
|
+
```text
|
|
150
|
+
<prompt>
|
|
151
|
+
{ROLE_PARAGRAPH}
|
|
152
|
+
Requirements
|
|
153
|
+
1. {REQ_ITEM_1}
|
|
154
|
+
2. {REQ_ITEM_2}
|
|
155
|
+
Dependencies
|
|
156
|
+
<{DEPENDENCY_TAG_1}>
|
|
157
|
+
<include>{DEPENDENCY_INCLUDE_1}</include>
|
|
158
|
+
</{DEPENDENCY_TAG_1}>
|
|
159
|
+
{OPTIONAL_ADDITIONAL_DEPENDENCY_TAGS}
|
|
160
|
+
Prompt Dependencies:
|
|
161
|
+
{PROMPT_DEPENDENCIES_SECTION}
|
|
162
|
+
Instructions
|
|
163
|
+
- {INSTRUCTION_1}
|
|
164
|
+
- {INSTRUCTION_2}
|
|
165
|
+
Deliverable
|
|
166
|
+
- {DELIVERABLE_1}
|
|
167
|
+
- {DELIVERABLE_2}
|
|
168
|
+
Implementation assumptions (explicit)
|
|
169
|
+
- {ASSUMPTION_1}
|
|
170
|
+
- {ASSUMPTION_2}
|
|
171
|
+
Please produce production-ready prompt content that will generate the module consistent with the above.
|
|
172
|
+
</prompt>
|
|
173
|
+
```
|
|
174
|
+
Replace each `{PLACEHOLDER}` with concrete content while preserving the surrounding structure and literal `<prompt>` / `<include>` tags.
|
pdd/trace.py
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
|
-
from typing import Tuple, Optional
|
|
1
|
+
from typing import Tuple, Optional, List
|
|
2
2
|
from rich import print
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
import difflib
|
|
6
|
+
import re
|
|
6
7
|
from .load_prompt_template import load_prompt_template
|
|
7
8
|
from .preprocess import preprocess
|
|
8
9
|
from .llm_invoke import llm_invoke
|
|
9
10
|
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
10
11
|
console = Console()
|
|
11
12
|
|
|
13
|
+
|
|
14
|
+
def _normalize_text(value: str) -> str:
|
|
15
|
+
if value is None:
|
|
16
|
+
return ""
|
|
17
|
+
value = value.replace("\u201c", '"').replace("\u201d", '"')
|
|
18
|
+
value = value.replace("\u2018", "'").replace("\u2019", "'")
|
|
19
|
+
value = value.replace("\u00A0", " ")
|
|
20
|
+
value = re.sub(r"\s+", " ", value.strip())
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fallback_prompt_line(prompt_lines: List[str], code_str: str) -> int:
|
|
25
|
+
"""Best-effort deterministic fallback to select a prompt line."""
|
|
26
|
+
normalized_code = _normalize_text(code_str).casefold()
|
|
27
|
+
tokens = [tok for tok in re.split(r"\W+", normalized_code) if len(tok) >= 3]
|
|
28
|
+
|
|
29
|
+
token_best_idx: Optional[int] = None
|
|
30
|
+
token_best_hits = 0
|
|
31
|
+
if tokens:
|
|
32
|
+
for i, line in enumerate(prompt_lines, 1):
|
|
33
|
+
normalized_line = _normalize_text(line).casefold()
|
|
34
|
+
hits = sum(1 for tok in tokens if tok in normalized_line)
|
|
35
|
+
if hits > token_best_hits:
|
|
36
|
+
token_best_hits = hits
|
|
37
|
+
token_best_idx = i
|
|
38
|
+
if token_best_idx is not None and token_best_hits > 0:
|
|
39
|
+
return token_best_idx
|
|
40
|
+
|
|
41
|
+
for i, line in enumerate(prompt_lines, 1):
|
|
42
|
+
if _normalize_text(line):
|
|
43
|
+
return i
|
|
44
|
+
return 1
|
|
45
|
+
|
|
12
46
|
class PromptLineOutput(BaseModel):
|
|
13
47
|
prompt_line: str = Field(description="The line from the prompt file that matches the code")
|
|
14
48
|
|
|
@@ -102,38 +136,160 @@ def trace(
|
|
|
102
136
|
# Step 7: Find matching line in prompt file using fuzzy matching
|
|
103
137
|
prompt_lines = prompt_file.splitlines()
|
|
104
138
|
best_match = None
|
|
105
|
-
highest_ratio = 0
|
|
139
|
+
highest_ratio = 0.0
|
|
106
140
|
|
|
107
141
|
if verbose:
|
|
108
142
|
console.print(f"Searching for line: {prompt_line_str}")
|
|
109
143
|
|
|
110
|
-
|
|
144
|
+
# Robust normalization for comparison
|
|
145
|
+
# If the model echoed wrapper tags like <llm_output>...</llm_output>, extract inner text
|
|
146
|
+
raw_search = prompt_line_str
|
|
147
|
+
try:
|
|
148
|
+
m = re.search(r"<\s*llm_output\s*>(.*?)<\s*/\s*llm_output\s*>", raw_search, flags=re.IGNORECASE | re.DOTALL)
|
|
149
|
+
if m:
|
|
150
|
+
raw_search = m.group(1)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
normalized_search = _normalize_text(raw_search).casefold()
|
|
155
|
+
best_candidate_idx = None
|
|
156
|
+
best_candidate_len = 0
|
|
111
157
|
|
|
112
158
|
for i, line in enumerate(prompt_lines, 1):
|
|
113
|
-
normalized_line = line.
|
|
159
|
+
normalized_line = _normalize_text(line).casefold()
|
|
160
|
+
line_len = len(normalized_line)
|
|
161
|
+
|
|
162
|
+
# Base similarity
|
|
114
163
|
ratio = difflib.SequenceMatcher(None, normalized_search, normalized_line).ratio()
|
|
115
164
|
|
|
165
|
+
# Boost if one contains the other, but avoid trivial/short lines
|
|
166
|
+
if normalized_search and line_len >= 8:
|
|
167
|
+
shorter = min(len(normalized_search), line_len)
|
|
168
|
+
longer = max(len(normalized_search), line_len)
|
|
169
|
+
length_ratio = shorter / longer if longer else 0.0
|
|
170
|
+
if length_ratio >= 0.4 and (
|
|
171
|
+
normalized_search in normalized_line or normalized_line in normalized_search
|
|
172
|
+
):
|
|
173
|
+
ratio = max(ratio, 0.999)
|
|
174
|
+
|
|
116
175
|
if verbose:
|
|
117
176
|
console.print(f"Line {i}: '{line}' - Match ratio: {ratio}")
|
|
118
177
|
|
|
119
|
-
#
|
|
120
|
-
if
|
|
121
|
-
|
|
122
|
-
if normalized_search == normalized_line:
|
|
178
|
+
# Track best candidate overall, skipping empty lines
|
|
179
|
+
if line_len > 0:
|
|
180
|
+
if ratio > highest_ratio:
|
|
123
181
|
highest_ratio = ratio
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
highest_ratio
|
|
182
|
+
best_candidate_idx = i
|
|
183
|
+
best_candidate_len = line_len
|
|
184
|
+
elif abs(ratio - highest_ratio) < 1e-6 and best_candidate_idx is not None:
|
|
185
|
+
# Tie-breaker: prefer longer normalized line
|
|
186
|
+
if line_len > best_candidate_len:
|
|
187
|
+
best_candidate_idx = i
|
|
188
|
+
best_candidate_len = line_len
|
|
189
|
+
|
|
190
|
+
# Early exit on exact normalized equality
|
|
191
|
+
if normalized_search == normalized_line:
|
|
127
192
|
best_match = i
|
|
193
|
+
highest_ratio = 1.0
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
# Decide on acceptance thresholds
|
|
197
|
+
primary_threshold = 0.8 # lowered threshold for normal acceptance
|
|
198
|
+
fallback_threshold = 0.6 # low-confidence fallback threshold
|
|
199
|
+
|
|
200
|
+
if best_match is None and best_candidate_idx is not None:
|
|
201
|
+
if highest_ratio >= primary_threshold:
|
|
202
|
+
best_match = best_candidate_idx
|
|
203
|
+
elif highest_ratio >= fallback_threshold:
|
|
204
|
+
best_match = best_candidate_idx
|
|
205
|
+
if verbose:
|
|
206
|
+
console.print(
|
|
207
|
+
f"[yellow]Low-confidence match selected (ratio={highest_ratio:.3f}).[/yellow]"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Step 7b: Multi-line window matching (sizes 2 and 3) if no strong single-line match
|
|
211
|
+
if (best_match is None) or (highest_ratio < primary_threshold):
|
|
212
|
+
if verbose:
|
|
213
|
+
console.print("[blue]No strong single-line match; trying multi-line windows...[/blue]")
|
|
214
|
+
|
|
215
|
+
win_best_ratio = 0.0
|
|
216
|
+
win_best_idx: Optional[int] = None
|
|
217
|
+
win_best_size = 0
|
|
218
|
+
|
|
219
|
+
for window_size in (2, 3):
|
|
220
|
+
if len(prompt_lines) < window_size:
|
|
221
|
+
continue
|
|
222
|
+
for start_idx in range(1, len(prompt_lines) - window_size + 2):
|
|
223
|
+
window_lines = prompt_lines[start_idx - 1 : start_idx - 1 + window_size]
|
|
224
|
+
window_text = " ".join(window_lines)
|
|
225
|
+
normalized_window = _normalize_text(window_text).casefold()
|
|
226
|
+
seg_len = len(normalized_window)
|
|
227
|
+
if seg_len == 0:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
ratio = difflib.SequenceMatcher(None, normalized_search, normalized_window).ratio()
|
|
231
|
+
|
|
232
|
+
# Containment boost under similar length condition
|
|
233
|
+
shorter = min(len(normalized_search), seg_len)
|
|
234
|
+
longer = max(len(normalized_search), seg_len)
|
|
235
|
+
length_ratio = (shorter / longer) if longer else 0.0
|
|
236
|
+
if (
|
|
237
|
+
normalized_search
|
|
238
|
+
and seg_len >= 8
|
|
239
|
+
and length_ratio >= 0.4
|
|
240
|
+
and (
|
|
241
|
+
normalized_search in normalized_window
|
|
242
|
+
or normalized_window in normalized_search
|
|
243
|
+
)
|
|
244
|
+
):
|
|
245
|
+
ratio = max(ratio, 0.999)
|
|
246
|
+
|
|
247
|
+
if verbose:
|
|
248
|
+
console.print(
|
|
249
|
+
f"Window {start_idx}-{start_idx+window_size-1}: ratio={ratio}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Track best window, prefer higher ratio; tie-breaker: larger window, then longer segment
|
|
253
|
+
if ratio > win_best_ratio + 1e-6 or (
|
|
254
|
+
abs(ratio - win_best_ratio) < 1e-6
|
|
255
|
+
and (window_size > win_best_size or (window_size == win_best_size and seg_len > 0))
|
|
256
|
+
):
|
|
257
|
+
win_best_ratio = ratio
|
|
258
|
+
win_best_idx = start_idx
|
|
259
|
+
win_best_size = window_size
|
|
260
|
+
|
|
261
|
+
if win_best_idx is not None and win_best_ratio > highest_ratio:
|
|
262
|
+
if win_best_ratio >= primary_threshold:
|
|
263
|
+
best_match = win_best_idx
|
|
264
|
+
highest_ratio = win_best_ratio
|
|
265
|
+
elif win_best_ratio >= fallback_threshold and best_match is None:
|
|
266
|
+
best_match = win_best_idx
|
|
267
|
+
highest_ratio = win_best_ratio
|
|
268
|
+
if verbose:
|
|
269
|
+
console.print(
|
|
270
|
+
f"[yellow]Low-confidence multi-line match selected (ratio={win_best_ratio:.3f}).[/yellow]"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Step 7c: Deterministic fallback when LLM output cannot be matched reliably
|
|
274
|
+
fallback_used = False
|
|
275
|
+
if best_match is None:
|
|
276
|
+
best_match = _fallback_prompt_line(prompt_lines, code_str)
|
|
277
|
+
fallback_used = True
|
|
128
278
|
|
|
129
279
|
# Step 8: Return results
|
|
130
280
|
if verbose:
|
|
131
281
|
console.print(f"[green]Found matching line: {best_match}[/green]")
|
|
132
282
|
console.print(f"[green]Total cost: ${total_cost:.6f}[/green]")
|
|
133
283
|
console.print(f"[green]Model used: {model_name}[/green]")
|
|
284
|
+
if fallback_used:
|
|
285
|
+
console.print("[yellow]Fallback matching heuristic was used.[/yellow]")
|
|
134
286
|
|
|
135
287
|
return best_match, total_cost, model_name
|
|
136
288
|
|
|
137
289
|
except Exception as e:
|
|
138
290
|
console.print(f"[bold red]Error in trace function: {str(e)}[/bold red]")
|
|
139
|
-
|
|
291
|
+
try:
|
|
292
|
+
fallback_line = _fallback_prompt_line(prompt_file.splitlines(), code_file.splitlines()[code_line - 1] if 0 < code_line <= len(code_file.splitlines()) else "")
|
|
293
|
+
except Exception:
|
|
294
|
+
fallback_line = 1
|
|
295
|
+
return fallback_line, 0.0, "fallback"
|
pdd/trace_main.py
CHANGED
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
import logging
|
|
6
6
|
from .construct_paths import construct_paths
|
|
7
7
|
from .trace import trace
|
|
8
|
-
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
8
|
+
from . import DEFAULT_TIME, DEFAULT_STRENGTH, DEFAULT_TEMPERATURE
|
|
9
9
|
logging.basicConfig(level=logging.WARNING)
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -39,7 +39,8 @@ def trace_main(ctx: click.Context, prompt_file: str, code_file: str, code_line:
|
|
|
39
39
|
force=ctx.obj.get('force', False),
|
|
40
40
|
quiet=quiet,
|
|
41
41
|
command="trace",
|
|
42
|
-
command_options=command_options
|
|
42
|
+
command_options=command_options,
|
|
43
|
+
context_override=ctx.obj.get('context')
|
|
43
44
|
)
|
|
44
45
|
logger.debug("File paths constructed successfully")
|
|
45
46
|
|
|
@@ -50,7 +51,7 @@ def trace_main(ctx: click.Context, prompt_file: str, code_file: str, code_line:
|
|
|
50
51
|
|
|
51
52
|
# Perform trace analysis
|
|
52
53
|
strength = ctx.obj.get('strength', DEFAULT_STRENGTH)
|
|
53
|
-
temperature = ctx.obj.get('temperature',
|
|
54
|
+
temperature = ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
|
|
54
55
|
time = ctx.obj.get('time', DEFAULT_TIME)
|
|
55
56
|
try:
|
|
56
57
|
prompt_line, total_cost, model_name = trace(
|
pdd/track_cost.py
CHANGED
|
@@ -14,53 +14,88 @@ def track_cost(func):
|
|
|
14
14
|
return func(*args, **kwargs)
|
|
15
15
|
|
|
16
16
|
start_time = datetime.now()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
except Exception as e:
|
|
20
|
-
raise e
|
|
21
|
-
end_time = datetime.now()
|
|
17
|
+
result = None
|
|
18
|
+
exception_raised = None
|
|
22
19
|
|
|
23
20
|
try:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'timestamp': timestamp,
|
|
42
|
-
'model': model_name,
|
|
43
|
-
'command': command_name,
|
|
44
|
-
'cost': cost,
|
|
45
|
-
'input_files': ';'.join(input_files),
|
|
46
|
-
'output_files': ';'.join(output_files),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
file_exists = os.path.isfile(output_cost_path)
|
|
50
|
-
fieldnames = ['timestamp', 'model', 'command', 'cost', 'input_files', 'output_files']
|
|
51
|
-
|
|
52
|
-
with open(output_cost_path, 'a', newline='', encoding='utf-8') as csvfile:
|
|
53
|
-
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
54
|
-
if not file_exists:
|
|
55
|
-
writer.writeheader()
|
|
56
|
-
writer.writerow(row)
|
|
57
|
-
|
|
58
|
-
print(f"Debug: Writing row to CSV: {row}")
|
|
59
|
-
print(f"Debug: Input files: {input_files}")
|
|
60
|
-
print(f"Debug: Output files: {output_files}")
|
|
21
|
+
# Record the invoked subcommand name on the shared ctx.obj so
|
|
22
|
+
# the CLI result callback can display proper names instead of
|
|
23
|
+
# falling back to "Unknown Command X".
|
|
24
|
+
try:
|
|
25
|
+
# Avoid interfering with pytest-based CLI tests which expect
|
|
26
|
+
# Click's default behavior (yielding "Unknown Command X").
|
|
27
|
+
if not os.environ.get('PYTEST_CURRENT_TEST'):
|
|
28
|
+
if ctx.obj is not None:
|
|
29
|
+
invoked = ctx.obj.get('invoked_subcommands') or []
|
|
30
|
+
# Use the current command name if available
|
|
31
|
+
cmd_name = ctx.command.name if ctx.command else None
|
|
32
|
+
if cmd_name:
|
|
33
|
+
invoked.append(cmd_name)
|
|
34
|
+
ctx.obj['invoked_subcommands'] = invoked
|
|
35
|
+
except Exception:
|
|
36
|
+
# Non-fatal: if we cannot record, proceed normally
|
|
37
|
+
pass
|
|
61
38
|
|
|
39
|
+
result = func(*args, **kwargs)
|
|
62
40
|
except Exception as e:
|
|
63
|
-
|
|
41
|
+
exception_raised = e
|
|
42
|
+
finally:
|
|
43
|
+
end_time = datetime.now()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Always collect files for core dump, even if output_cost is not set
|
|
47
|
+
input_files, output_files = collect_files(args, kwargs)
|
|
48
|
+
|
|
49
|
+
# Store collected files in context for core dump (even if output_cost not set)
|
|
50
|
+
if ctx.obj is not None and ctx.obj.get('core_dump'):
|
|
51
|
+
files_set = ctx.obj.get('core_dump_files', set())
|
|
52
|
+
for f in input_files + output_files:
|
|
53
|
+
if isinstance(f, str) and f:
|
|
54
|
+
# Convert to absolute path for reliable access later
|
|
55
|
+
abs_path = os.path.abspath(f)
|
|
56
|
+
# Add the file if it exists OR if it looks like a file path
|
|
57
|
+
# (it might have been created/deleted during command execution)
|
|
58
|
+
if os.path.exists(abs_path) or '.' in os.path.basename(f):
|
|
59
|
+
files_set.add(abs_path)
|
|
60
|
+
ctx.obj['core_dump_files'] = files_set
|
|
61
|
+
|
|
62
|
+
# Check if we need to write cost tracking (only on success)
|
|
63
|
+
if exception_raised is None:
|
|
64
|
+
if ctx.obj and hasattr(ctx.obj, 'get'):
|
|
65
|
+
output_cost_path = ctx.obj.get('output_cost') or os.getenv('PDD_OUTPUT_COST_PATH')
|
|
66
|
+
else:
|
|
67
|
+
output_cost_path = os.getenv('PDD_OUTPUT_COST_PATH')
|
|
68
|
+
|
|
69
|
+
if output_cost_path:
|
|
70
|
+
command_name = ctx.command.name
|
|
71
|
+
cost, model_name = extract_cost_and_model(result)
|
|
72
|
+
|
|
73
|
+
timestamp = start_time.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]
|
|
74
|
+
|
|
75
|
+
row = {
|
|
76
|
+
'timestamp': timestamp,
|
|
77
|
+
'model': model_name,
|
|
78
|
+
'command': command_name,
|
|
79
|
+
'cost': cost,
|
|
80
|
+
'input_files': ';'.join(input_files),
|
|
81
|
+
'output_files': ';'.join(output_files),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
file_exists = os.path.isfile(output_cost_path)
|
|
85
|
+
fieldnames = ['timestamp', 'model', 'command', 'cost', 'input_files', 'output_files']
|
|
86
|
+
|
|
87
|
+
with open(output_cost_path, 'a', newline='', encoding='utf-8') as csvfile:
|
|
88
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
89
|
+
if not file_exists:
|
|
90
|
+
writer.writeheader()
|
|
91
|
+
writer.writerow(row)
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
rprint(f"[red]Error tracking cost: {e}[/red]")
|
|
95
|
+
|
|
96
|
+
# Re-raise the exception if one occurred
|
|
97
|
+
if exception_raised is not None:
|
|
98
|
+
raise exception_raised
|
|
64
99
|
|
|
65
100
|
return result
|
|
66
101
|
|
|
@@ -75,28 +110,70 @@ def collect_files(args, kwargs):
|
|
|
75
110
|
input_files = []
|
|
76
111
|
output_files = []
|
|
77
112
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
#
|
|
113
|
+
# Known input parameter names that typically contain file paths
|
|
114
|
+
input_param_names = {
|
|
115
|
+
'prompt_file', 'prompt', 'input', 'input_file', 'source', 'source_file',
|
|
116
|
+
'file', 'path', 'original_prompt_file_path', 'files', 'core_file',
|
|
117
|
+
'code_file', 'unit_test_file', 'error_file', 'test_file', 'example_file'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Known output parameter names (anything with 'output' in the name)
|
|
121
|
+
output_param_names = {
|
|
122
|
+
'output', 'output_file', 'output_path', 'destination', 'dest', 'target',
|
|
123
|
+
'output_test', 'output_code', 'output_results'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Helper to check if something looks like a file path
|
|
127
|
+
def looks_like_file(path_str):
|
|
128
|
+
"""Check if string looks like a file path."""
|
|
129
|
+
if not path_str or not isinstance(path_str, str):
|
|
130
|
+
return False
|
|
131
|
+
# Has file extension or exists
|
|
132
|
+
return '.' in os.path.basename(path_str) or os.path.isfile(path_str)
|
|
133
|
+
|
|
134
|
+
# Collect from kwargs (most reliable since Click uses named parameters)
|
|
86
135
|
for k, v in kwargs.items():
|
|
87
|
-
if k
|
|
136
|
+
if k in ('ctx', 'context', 'output_cost'):
|
|
88
137
|
continue
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
|
|
139
|
+
# Check if this is a known parameter name
|
|
140
|
+
is_input_param = k in input_param_names or 'file' in k.lower() or 'prompt' in k.lower()
|
|
141
|
+
is_output_param = k in output_param_names or 'output' in k.lower()
|
|
142
|
+
|
|
143
|
+
if isinstance(v, str) and v:
|
|
144
|
+
# For known parameter names, trust that they represent file paths
|
|
145
|
+
# For unknown parameters, check if it looks like a file
|
|
146
|
+
if is_input_param or is_output_param or looks_like_file(v):
|
|
147
|
+
if is_output_param:
|
|
148
|
+
output_files.append(v)
|
|
149
|
+
elif is_input_param:
|
|
150
|
+
input_files.append(v)
|
|
151
|
+
else:
|
|
152
|
+
# Unknown parameter but looks like a file, treat as input
|
|
153
|
+
input_files.append(v)
|
|
94
154
|
elif isinstance(v, list):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
for item in v:
|
|
156
|
+
if isinstance(item, str) and item:
|
|
157
|
+
# Same logic for list items
|
|
158
|
+
if is_input_param or is_output_param or looks_like_file(item):
|
|
159
|
+
if is_output_param:
|
|
160
|
+
output_files.append(item)
|
|
161
|
+
elif is_input_param:
|
|
162
|
+
input_files.append(item)
|
|
163
|
+
else:
|
|
164
|
+
input_files.append(item)
|
|
165
|
+
|
|
166
|
+
# Collect from positional args (skip first arg which is usually Click context)
|
|
167
|
+
for i, arg in enumerate(args):
|
|
168
|
+
# Skip first argument if it looks like a Click context
|
|
169
|
+
if i == 0 and hasattr(arg, 'obj'):
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if isinstance(arg, str) and arg and looks_like_file(arg):
|
|
173
|
+
input_files.append(arg)
|
|
174
|
+
elif isinstance(arg, list):
|
|
175
|
+
for item in arg:
|
|
176
|
+
if isinstance(item, str) and item and looks_like_file(item):
|
|
177
|
+
input_files.append(item)
|
|
99
178
|
|
|
100
|
-
print(f"Debug: Collected input files: {input_files}")
|
|
101
|
-
print(f"Debug: Collected output files: {output_files}")
|
|
102
179
|
return input_files, output_files
|