devdox-sonar 0.0.2__tar.gz → 0.0.4__tar.gz

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 (58) hide show
  1. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/PKG-INFO +59 -68
  2. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/README.md +56 -67
  3. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/pyproject.toml +3 -1
  4. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/cli.py +94 -55
  5. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/config.py +1 -1
  6. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/llm_fixer.py +64 -33
  7. devdox_sonar-0.0.4/src/devdox_ai_sonar/models/constant_naming.py +36 -0
  8. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/models/file_structures.py +2 -1
  9. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/models/sonar.py +78 -0
  10. devdox_sonar-0.0.4/src/devdox_ai_sonar/prompts/python/constant_namer_system.j2 +18 -0
  11. devdox_sonar-0.0.4/src/devdox_ai_sonar/prompts/python/constant_namer_user.j2 +20 -0
  12. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/prompts/python/refactoring/user_prompt.j2 +3 -2
  13. devdox_sonar-0.0.4/src/devdox_ai_sonar/services/constant_namer.py +416 -0
  14. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/services/extractor.py +17 -7
  15. devdox_sonar-0.0.4/src/devdox_ai_sonar/services/rule_handler.py +1557 -0
  16. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/sonar_analyzer.py +155 -9
  17. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/file_indentation.py +248 -17
  18. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/function_finder.py +151 -15
  19. devdox_sonar-0.0.4/src/devdox_ai_sonar/utils/supported_programming_languages.py +241 -0
  20. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/PKG-INFO +59 -68
  21. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/SOURCES.txt +5 -1
  22. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/requires.txt +2 -0
  23. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_cli.py +167 -36
  24. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_config.py +2 -2
  25. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_fix_application.py +1 -0
  26. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_llm_fixer.py +4 -4
  27. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_sonar_analyzer.py +276 -0
  28. devdox_sonar-0.0.2/src/devdox_ai_sonar/services/rule_handler.py +0 -749
  29. devdox_sonar-0.0.2/src/devdox_ai_sonar/utils/file_filter.py +0 -58
  30. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/LICENSE +0 -0
  31. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/setup.cfg +0 -0
  32. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/__init__.py +0 -0
  33. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/fix_validator.py +0 -0
  34. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/models/llm.py +0 -0
  35. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/models/llm_config.py +0 -0
  36. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/prompts/python/refactoring/system_fix_issues.j2 +0 -0
  37. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/prompts/python/system_fix_issues.j2 +0 -0
  38. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/prompts/python/user_prompt.j2 +0 -0
  39. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/prompts/python/validator.j2 +0 -0
  40. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/services/configuration.py +0 -0
  41. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/services/rule_analyzer.py +0 -0
  42. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/sonar_fetcher.py +0 -0
  43. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/templates/md.j2 +0 -0
  44. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/__init__.py +0 -0
  45. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/async_file_io.py +0 -0
  46. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/constant.py +0 -0
  47. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/exceptions.py +0 -0
  48. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/provider_config.py +0 -0
  49. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/result.py +0 -0
  50. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/sonar_config.py +0 -0
  51. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/ui.py +0 -0
  52. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_ai_sonar/utils/validator.py +0 -0
  53. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/dependency_links.txt +0 -0
  54. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/entry_points.txt +0 -0
  55. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/src/devdox_sonar.egg-info/top_level.txt +0 -0
  56. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_fix_validator.py +0 -0
  57. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_integration.py +0 -0
  58. {devdox_sonar-0.0.2 → devdox_sonar-0.0.4}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devdox_sonar
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: A CLI tool to analyze SonarCloud issues and attempt LLM-powered fixes
5
5
  Author-email: Hayat Bourji <hayat.bourgi@montyholding.com>
6
6
  Maintainer-email: Hayat Bourji <hayat.bourgi@montyholding.com>
@@ -48,6 +48,8 @@ Requires-Dist: tomlkit==0.13.3
48
48
  Requires-Dist: inquirer==3.4.1
49
49
  Requires-Dist: aiofiles==25.1.0
50
50
  Requires-Dist: langchain-core<1.0.0,>=0.3.78
51
+ Requires-Dist: yake>=0.4.8
52
+ Requires-Dist: langdetect>=1.0.9
51
53
  Provides-Extra: dev
52
54
  Requires-Dist: pytest==8.4.2; extra == "dev"
53
55
  Requires-Dist: black==25.11.0; extra == "dev"
@@ -149,33 +151,18 @@ devdox_sonar
149
151
 
150
152
  SonarCloud must have already scanned your project before DevDox AI Sonar can do anything. The tool reads SonarCloud's existing analysis report — it does not perform its own code analysis.
151
153
 
152
- ```mermaid
153
- flowchart LR
154
- A["SonarCloud\n(already scanned)"]
155
- B["Fetch Issues\nfrom report"]
156
- C["Clone Repo\nto /tmp"]
157
- D["Extract Code\n+ Context"]
158
- E["Build Prompt\n(Jinja2)"]
159
- F["Call LLM"]
160
- G["Validate Fix"]
161
- H{"Preview"}
162
- I["Apply +\nChangelog"]
163
- J["Skip"]
164
-
165
- A -->|"analysis\nreport"| B --> C --> D --> E --> F --> G --> H
166
- H -->|"apply = 1"| I
167
- H -->|"apply = 0"| J
168
-
169
- style A fill:#4a90d9,stroke:#2c6faa,color:#fff
170
- style B fill:#7b68ee,stroke:#5b48ce,color:#fff
171
- style C fill:#7b68ee,stroke:#5b48ce,color:#fff
172
- style D fill:#7b68ee,stroke:#5b48ce,color:#fff
173
- style E fill:#7b68ee,stroke:#5b48ce,color:#fff
174
- style F fill:#f5a623,stroke:#d4891c,color:#fff
175
- style G fill:#e67e22,stroke:#c96e1c,color:#fff
176
- style H fill:#e8e8e8,stroke:#999,color:#333
177
- style I fill:#50c878,stroke:#3da85e,color:#fff
178
- style J fill:#ccc,stroke:#999,color:#666
154
+ ```
155
+ SonarCloud Fetch Issues Clone Repo Extract Code Build Prompt
156
+ (already scanned) ──► from report ────► to /tmp ────────► + Context ────────► (Jinja2)
157
+
158
+
159
+ ┌── Apply + Changelog ◄── YES ── Preview ◄── Validate ◄── Call LLM
160
+ │ │
161
+ │ NO
162
+ │ │
163
+ │ ▼
164
+ │ Skip
165
+
179
166
  ```
180
167
 
181
168
  1. **Fetch** — Authenticates with SonarCloud and reads the analysis report via the Issues API. Filters by the types and severities you configured. Regular issues (bugs, code smells) are grouped **by rule** so all issues of the same kind are batched together. Security issues are grouped **by file**.
@@ -463,45 +450,49 @@ These options can be passed with any direct mode command to override your saved
463
450
 
464
451
  ### The fix_issues Pipeline
465
452
 
466
- ```mermaid
467
- flowchart TD
468
- A["Load Config\nauth.json + config.toml"]
469
- B["Clone Repo\nGit clone to /tmp"]
470
- C["Fetch Issues\nfrom SonarCloud report\nFilter by type/severity"]
471
- D["Group by Rule"]
472
-
473
- subgraph LOOP ["For Each Rule Group"]
474
- direction TB
475
- E["Extract Code\nLocate lines + context"]
476
- F["Select Handler"]
477
- G["Generate Fix\nLLM or AST-based"]
478
- H{"Preview\nFile, confidence,\nexplanation"}
479
- I["Apply + Validate"]
480
- J["Skip"]
481
- K{"Continue to\nnext issue?"}
482
- end
483
-
484
- L["Write Changelog\nCHANGES_REGULAR_*.md"]
485
-
486
- A --> B --> C --> D --> E
487
- E --> F --> G --> H
488
- H -->|"apply = 1"| I --> K
489
- H -->|"apply = 0"| J --> K
490
- K -->|Yes| E
491
- K -->|No| L
492
-
493
- style A fill:#4a90d9,stroke:#2c6faa,color:#fff
494
- style B fill:#4a90d9,stroke:#2c6faa,color:#fff
495
- style C fill:#4a90d9,stroke:#2c6faa,color:#fff
496
- style D fill:#7b68ee,stroke:#5b48ce,color:#fff
497
- style E fill:#9b59b6,stroke:#7d3c98,color:#fff
498
- style F fill:#9b59b6,stroke:#7d3c98,color:#fff
499
- style G fill:#f5a623,stroke:#d4891c,color:#fff
500
- style H fill:#e8e8e8,stroke:#999,color:#333
501
- style I fill:#50c878,stroke:#3da85e,color:#fff
502
- style J fill:#ccc,stroke:#999,color:#666
503
- style K fill:#e8e8e8,stroke:#999,color:#333
504
- style L fill:#50c878,stroke:#3da85e,color:#fff
453
+ ```
454
+ Load Config (auth.json + config.toml)
455
+
456
+
457
+ Clone Repo (git clone to /tmp)
458
+
459
+
460
+ Fetch Issues from SonarCloud report
461
+ Filter by type / severity
462
+
463
+
464
+ Group by Rule
465
+
466
+
467
+ ┌────────────────────────────────────────┐
468
+ │ For Each Rule Group │
469
+ │ │
470
+ │ Extract Code (locate lines + context) │
471
+ │ │ │
472
+ │ ▼ │
473
+ │ Select Handler │
474
+ │ │ │
475
+ │ ▼ │
476
+ │ Generate Fix (LLM or AST-based) │
477
+ │ │ │
478
+ │ ▼ │
479
+ │ Preview (file, confidence, explain) │
480
+ │ / \ │
481
+ │ apply=1 apply=0 │
482
+ │ │ │ │
483
+ │ ▼ ▼ │
484
+ │ Apply + Skip │
485
+ │ Validate │ │
486
+ │ │ │ │
487
+ │ └─────┬─────┘ │
488
+ │ ▼ │
489
+ │ Continue to next issue? ─► YES ─┐ │
490
+ │ │ │ │
491
+ │ NO (loop)│ │
492
+ │ │ │ │
493
+ └────────────┼────────────────────────┘ │
494
+ ▼ │
495
+ Write Changelog (CHANGES_REGULAR_*.md) │
505
496
  ```
506
497
 
507
498
  **Specialized rule handlers** — Most rules go through the LLM via `DefaultRuleHandler`. Two rules have dedicated handlers:
@@ -719,7 +710,7 @@ This project is licensed under the [Apache License 2.0](LICENSE). You are free t
719
710
 
720
711
  ## Authors
721
712
 
722
- Created and maintained by **Hayat Bourji** (hayat.bourgi@montyholding.com) at [Monty Mobile](https://github.com/montymobile1).
713
+ Created and maintained by **Hayat Bourji** (hayat.bourgi@montyholding.com) and **Mohammad Jaafar** (mohamadali.jaafar@montymobile.com) at [Monty Mobile](https://github.com/montymobile1).
723
714
 
724
715
  ## Acknowledgments
725
716
 
@@ -84,33 +84,18 @@ devdox_sonar
84
84
 
85
85
  SonarCloud must have already scanned your project before DevDox AI Sonar can do anything. The tool reads SonarCloud's existing analysis report — it does not perform its own code analysis.
86
86
 
87
- ```mermaid
88
- flowchart LR
89
- A["SonarCloud\n(already scanned)"]
90
- B["Fetch Issues\nfrom report"]
91
- C["Clone Repo\nto /tmp"]
92
- D["Extract Code\n+ Context"]
93
- E["Build Prompt\n(Jinja2)"]
94
- F["Call LLM"]
95
- G["Validate Fix"]
96
- H{"Preview"}
97
- I["Apply +\nChangelog"]
98
- J["Skip"]
99
-
100
- A -->|"analysis\nreport"| B --> C --> D --> E --> F --> G --> H
101
- H -->|"apply = 1"| I
102
- H -->|"apply = 0"| J
103
-
104
- style A fill:#4a90d9,stroke:#2c6faa,color:#fff
105
- style B fill:#7b68ee,stroke:#5b48ce,color:#fff
106
- style C fill:#7b68ee,stroke:#5b48ce,color:#fff
107
- style D fill:#7b68ee,stroke:#5b48ce,color:#fff
108
- style E fill:#7b68ee,stroke:#5b48ce,color:#fff
109
- style F fill:#f5a623,stroke:#d4891c,color:#fff
110
- style G fill:#e67e22,stroke:#c96e1c,color:#fff
111
- style H fill:#e8e8e8,stroke:#999,color:#333
112
- style I fill:#50c878,stroke:#3da85e,color:#fff
113
- style J fill:#ccc,stroke:#999,color:#666
87
+ ```
88
+ SonarCloud Fetch Issues Clone Repo Extract Code Build Prompt
89
+ (already scanned) ──► from report ────► to /tmp ────────► + Context ────────► (Jinja2)
90
+
91
+
92
+ ┌── Apply + Changelog ◄── YES ── Preview ◄── Validate ◄── Call LLM
93
+ │ │
94
+ │ NO
95
+ │ │
96
+ │ ▼
97
+ │ Skip
98
+
114
99
  ```
115
100
 
116
101
  1. **Fetch** — Authenticates with SonarCloud and reads the analysis report via the Issues API. Filters by the types and severities you configured. Regular issues (bugs, code smells) are grouped **by rule** so all issues of the same kind are batched together. Security issues are grouped **by file**.
@@ -398,45 +383,49 @@ These options can be passed with any direct mode command to override your saved
398
383
 
399
384
  ### The fix_issues Pipeline
400
385
 
401
- ```mermaid
402
- flowchart TD
403
- A["Load Config\nauth.json + config.toml"]
404
- B["Clone Repo\nGit clone to /tmp"]
405
- C["Fetch Issues\nfrom SonarCloud report\nFilter by type/severity"]
406
- D["Group by Rule"]
407
-
408
- subgraph LOOP ["For Each Rule Group"]
409
- direction TB
410
- E["Extract Code\nLocate lines + context"]
411
- F["Select Handler"]
412
- G["Generate Fix\nLLM or AST-based"]
413
- H{"Preview\nFile, confidence,\nexplanation"}
414
- I["Apply + Validate"]
415
- J["Skip"]
416
- K{"Continue to\nnext issue?"}
417
- end
418
-
419
- L["Write Changelog\nCHANGES_REGULAR_*.md"]
420
-
421
- A --> B --> C --> D --> E
422
- E --> F --> G --> H
423
- H -->|"apply = 1"| I --> K
424
- H -->|"apply = 0"| J --> K
425
- K -->|Yes| E
426
- K -->|No| L
427
-
428
- style A fill:#4a90d9,stroke:#2c6faa,color:#fff
429
- style B fill:#4a90d9,stroke:#2c6faa,color:#fff
430
- style C fill:#4a90d9,stroke:#2c6faa,color:#fff
431
- style D fill:#7b68ee,stroke:#5b48ce,color:#fff
432
- style E fill:#9b59b6,stroke:#7d3c98,color:#fff
433
- style F fill:#9b59b6,stroke:#7d3c98,color:#fff
434
- style G fill:#f5a623,stroke:#d4891c,color:#fff
435
- style H fill:#e8e8e8,stroke:#999,color:#333
436
- style I fill:#50c878,stroke:#3da85e,color:#fff
437
- style J fill:#ccc,stroke:#999,color:#666
438
- style K fill:#e8e8e8,stroke:#999,color:#333
439
- style L fill:#50c878,stroke:#3da85e,color:#fff
386
+ ```
387
+ Load Config (auth.json + config.toml)
388
+
389
+
390
+ Clone Repo (git clone to /tmp)
391
+
392
+
393
+ Fetch Issues from SonarCloud report
394
+ Filter by type / severity
395
+
396
+
397
+ Group by Rule
398
+
399
+
400
+ ┌────────────────────────────────────────┐
401
+ │ For Each Rule Group │
402
+ │ │
403
+ │ Extract Code (locate lines + context) │
404
+ │ │ │
405
+ │ ▼ │
406
+ │ Select Handler │
407
+ │ │ │
408
+ │ ▼ │
409
+ │ Generate Fix (LLM or AST-based) │
410
+ │ │ │
411
+ │ ▼ │
412
+ │ Preview (file, confidence, explain) │
413
+ │ / \ │
414
+ │ apply=1 apply=0 │
415
+ │ │ │ │
416
+ │ ▼ ▼ │
417
+ │ Apply + Skip │
418
+ │ Validate │ │
419
+ │ │ │ │
420
+ │ └─────┬─────┘ │
421
+ │ ▼ │
422
+ │ Continue to next issue? ─► YES ─┐ │
423
+ │ │ │ │
424
+ │ NO (loop)│ │
425
+ │ │ │ │
426
+ └────────────┼────────────────────────┘ │
427
+ ▼ │
428
+ Write Changelog (CHANGES_REGULAR_*.md) │
440
429
  ```
441
430
 
442
431
  **Specialized rule handlers** — Most rules go through the LLM via `DefaultRuleHandler`. Two rules have dedicated handlers:
@@ -654,7 +643,7 @@ This project is licensed under the [Apache License 2.0](LICENSE). You are free t
654
643
 
655
644
  ## Authors
656
645
 
657
- Created and maintained by **Hayat Bourji** (hayat.bourgi@montyholding.com) at [Monty Mobile](https://github.com/montymobile1).
646
+ Created and maintained by **Hayat Bourji** (hayat.bourgi@montyholding.com) and **Mohammad Jaafar** (mohamadali.jaafar@montymobile.com) at [Monty Mobile](https://github.com/montymobile1).
658
647
 
659
648
  ## Acknowledgments
660
649
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devdox_sonar"
7
- version = "0.0.2"
7
+ version = "0.0.4"
8
8
  description = "A CLI tool to analyze SonarCloud issues and attempt LLM-powered fixes"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -58,6 +58,8 @@ dependencies = [
58
58
  "inquirer==3.4.1",
59
59
  "aiofiles==25.1.0",
60
60
  "langchain-core>=0.3.78,<1.0.0", # Satisfy langchain-openai requirements
61
+ "yake>=0.4.8",
62
+ "langdetect>=1.0.9",
61
63
  ]
62
64
 
63
65
  [project.optional-dependencies]
@@ -27,9 +27,9 @@ from devdox_ai_sonar.llm_fixer import LLMFixer
27
27
  from devdox_ai_sonar.models.llm_config import ConfigManager
28
28
  from devdox_ai_sonar.models.llm import ProviderType
29
29
  from devdox_ai_sonar.utils.file_indentation import (
30
- remove_tmp_files,
31
30
  download_latest_version,
32
- generate_tmp_path,
31
+ TmpCloneManager,
32
+ sweep_orphaned_tmp_dirs,
33
33
  )
34
34
  from devdox_ai_sonar.utils.validator import InputValidator, IssueType
35
35
  from devdox_ai_sonar.utils.sonar_config import SonarCloudConfigUI
@@ -47,7 +47,11 @@ from devdox_ai_sonar.utils.provider_config import (
47
47
  )
48
48
 
49
49
  from devdox_ai_sonar.utils.exceptions import SwitchCommandException
50
- from devdox_ai_sonar.utils.file_filter import is_file_processable
50
+ from devdox_ai_sonar.utils.supported_programming_languages import (
51
+ PYTHON,
52
+ IPYTHON,
53
+ is_file_processable,
54
+ )
51
55
  from devdox_ai_sonar.utils.ui import smart_prompt, smart_confirm
52
56
  from devdox_ai_sonar.utils import constant
53
57
  from devdox_ai_sonar.config import settings
@@ -57,6 +61,18 @@ EXCLUDE_RULE_CONFIG_FIELD = "configuration.exclude_rules"
57
61
  console = Console()
58
62
 
59
63
 
64
+ def _should_skip_file(file_path: Optional[str]) -> bool:
65
+ """Return True if *file_path* should be skipped during processing."""
66
+ return bool(
67
+ file_path
68
+ and not is_file_processable(
69
+ file_path,
70
+ allowed_suffixes=PYTHON.file_extensions | IPYTHON.file_extensions,
71
+ excluded_prefixes={"test_"},
72
+ )
73
+ )
74
+
75
+
60
76
  def async_command(f: Any) -> Any:
61
77
  """Decorator to run async functions with Click."""
62
78
 
@@ -750,7 +766,10 @@ async def main( # ← Async main
750
766
  "pull_request": pull_request,
751
767
  "excluded_rules": excluded_rules,
752
768
  }
753
- print("llm_api_key ", llm_api_key)
769
+
770
+ # Sweep orphaned temp directories from previous crashed runs
771
+ sweep_orphaned_tmp_dirs(on_status=lambda msg: console.print(f"[dim]{msg}[/dim]"))
772
+
754
773
  # If command specified, run it directly
755
774
  if command:
756
775
  await _execute_command_async(ctx, command)
@@ -1088,6 +1107,7 @@ async def _run_fix_issues(**kwargs: Any) -> None:
1088
1107
  issue_type=IssueType.REGULAR,
1089
1108
  download_latest=True,
1090
1109
  system_ask=True,
1110
+ check_tmp_path=True,
1091
1111
  )
1092
1112
 
1093
1113
  except SwitchCommandException:
@@ -1130,6 +1150,7 @@ async def _run_fix_security_issues(**kwargs: Any) -> None:
1130
1150
  issue_type=IssueType.SECURITY,
1131
1151
  download_latest=True,
1132
1152
  system_ask=True,
1153
+ check_tmp_path=True,
1133
1154
  )
1134
1155
 
1135
1156
  except SwitchCommandException:
@@ -1292,6 +1313,7 @@ async def fix_multiple(**kwargs: Any) -> None:
1292
1313
  issue_type=IssueType.REGULAR,
1293
1314
  download_latest=False,
1294
1315
  system_ask=False,
1316
+ check_tmp_path=False,
1295
1317
  )
1296
1318
  # security issues
1297
1319
  await _process_and_fix_issues(
@@ -1303,6 +1325,7 @@ async def fix_multiple(**kwargs: Any) -> None:
1303
1325
  issue_type=IssueType.SECURITY,
1304
1326
  download_latest=False,
1305
1327
  system_ask=False,
1328
+ check_tmp_path=False,
1306
1329
  )
1307
1330
 
1308
1331
  except SwitchCommandException:
@@ -1419,58 +1442,66 @@ async def _process_and_fix_issues(
1419
1442
  issue_type: IssueType = IssueType.REGULAR,
1420
1443
  download_latest: bool = True,
1421
1444
  system_ask: bool = True,
1445
+ check_tmp_path: bool = True,
1422
1446
  ) -> None:
1423
1447
  """Process and fix issues - Refactored."""
1424
-
1425
1448
  services = _initialize_fix_services(auth_config, llm_config)
1426
1449
 
1450
+ branch_downloaded = branch
1427
1451
  if download_latest:
1428
- tmp_path = generate_tmp_path()
1429
- branch_downloaded = branch
1430
1452
  if pull_request and int(pull_request) > 0:
1431
1453
  branch_downloaded = services["analyzer"].get_branch_from_pr(
1432
1454
  project_key=auth_config.project, pull_request=pull_request
1433
1455
  )
1434
-
1435
1456
  if not branch_downloaded:
1436
1457
  console.print("[red]Could not determine branch to download[/red]")
1437
1458
  raise click.Abort()
1438
1459
 
1439
- console.print(f"Cloning {auth_config.project} to {tmp_path}")
1440
- downloaded = download_latest_version(
1441
- auth_config.git_url, tmp_path, branch_downloaded
1460
+ # Orphaned temp dirs (from SIGKILL/OOM) are cleaned by
1461
+ # sweep_orphaned_tmp_dirs() at CLI startup.
1462
+ async with TmpCloneManager(
1463
+ on_cleanup=lambda p: console.print(
1464
+ f"[dim]Cleaning up temporary files: {p}[/dim]"
1442
1465
  )
1443
- if not downloaded:
1444
- console.print("Not able to download latest version")
1445
- raise click.Abort()
1446
- else:
1447
- tmp_path = str(auth_config.project_path)
1448
- # Fetch issues based on type
1449
- issues = _fetch_issues_by_type(
1450
- services["analyzer"], auth_config, branch, pull_request, fix_params, issue_type
1451
- )
1452
- if not issues:
1453
- msg = (
1454
- "No fixable security issues found"
1455
- if issue_type == IssueType.SECURITY
1456
- else "No fixable issues found"
1466
+ ) as tmp_path:
1467
+ if download_latest:
1468
+ console.print(f"Cloning {auth_config.project} to {tmp_path}")
1469
+ downloaded = download_latest_version(
1470
+ auth_config.git_url, str(tmp_path), branch_downloaded
1471
+ )
1472
+ if not downloaded:
1473
+ console.print("Not able to download latest version")
1474
+ raise click.Abort()
1475
+
1476
+ issues = _fetch_issues_by_type(
1477
+ services["analyzer"],
1478
+ auth_config,
1479
+ branch,
1480
+ pull_request,
1481
+ fix_params,
1482
+ issue_type,
1457
1483
  )
1458
- console.print(f"[yellow]{msg}[/yellow]")
1459
- remove_tmp_files(tmp_path)
1460
- return
1461
- total_issues = sum(len(issue_list) for issue_list in issues.values())
1484
+ if not issues:
1485
+ msg = (
1486
+ "No fixable security issues found"
1487
+ if issue_type == IssueType.SECURITY
1488
+ else "No fixable issues found"
1489
+ )
1490
+ console.print(f"[yellow]{msg}[/yellow]")
1491
+ return
1462
1492
 
1463
- console.print(f"\n[green]✓ Found {total_issues} fixable issues[/green]\n")
1464
- await _process_files_with_issues(
1465
- issues,
1466
- services,
1467
- auth_config,
1468
- fix_params,
1469
- issue_type,
1470
- Path(tmp_path),
1471
- system_ask=system_ask,
1472
- )
1473
- remove_tmp_files(tmp_path)
1493
+ total_issues = sum(len(issue_list) for issue_list in issues.values())
1494
+ console.print(f"\n[green]✓ Found {total_issues} fixable issues[/green]\n")
1495
+ await _process_files_with_issues(
1496
+ issues,
1497
+ services,
1498
+ auth_config,
1499
+ fix_params,
1500
+ issue_type,
1501
+ tmp_path,
1502
+ system_ask=system_ask,
1503
+ check_tmp_path=check_tmp_path,
1504
+ )
1474
1505
 
1475
1506
 
1476
1507
  def _initialize_fix_services(
@@ -1498,6 +1529,7 @@ async def _process_files_with_issues(
1498
1529
  issue_type: IssueType,
1499
1530
  tmp_path: Path,
1500
1531
  system_ask: bool = True,
1532
+ check_tmp_path: bool = True,
1501
1533
  ) -> None:
1502
1534
  """
1503
1535
  Process files with issues.
@@ -1511,7 +1543,13 @@ async def _process_files_with_issues(
1511
1543
  )
1512
1544
  if issue_type == IssueType.SECURITY:
1513
1545
  await _process_security_issues(
1514
- issues_by_file, services, auth_config, fix_params, md_file_path, tmp_path
1546
+ issues_by_file,
1547
+ services,
1548
+ auth_config,
1549
+ fix_params,
1550
+ md_file_path,
1551
+ tmp_path,
1552
+ check_tmp_path,
1515
1553
  )
1516
1554
  else:
1517
1555
  issues_by_rule_nested = {
@@ -1525,6 +1563,7 @@ async def _process_files_with_issues(
1525
1563
  md_file_path,
1526
1564
  tmp_path,
1527
1565
  system_ask,
1566
+ check_tmp_path,
1528
1567
  )
1529
1568
 
1530
1569
 
@@ -1536,6 +1575,7 @@ async def _process_regular_issues(
1536
1575
  md_file_path: Path,
1537
1576
  tmp_path: Path,
1538
1577
  system_ask: bool = True,
1578
+ check_tmp_path: bool = True,
1539
1579
  ) -> None:
1540
1580
  """
1541
1581
  Process regular issues grouped by rule.
@@ -1559,6 +1599,7 @@ async def _process_regular_issues(
1559
1599
  md_file_path,
1560
1600
  tmp_path,
1561
1601
  system_ask,
1602
+ check_tmp_path,
1562
1603
  )
1563
1604
 
1564
1605
  if not success:
@@ -1574,6 +1615,7 @@ async def _process_issues_for_rule(
1574
1615
  md_file_path: Path,
1575
1616
  tmp_path: Path,
1576
1617
  system_ask: bool = True,
1618
+ check_tmp_path: bool = True,
1577
1619
  ) -> bool:
1578
1620
  """
1579
1621
  Process all issues for a specific rule.
@@ -1586,13 +1628,7 @@ async def _process_issues_for_rule(
1586
1628
  total_issues = len(issues_list)
1587
1629
 
1588
1630
  for idx, issue in enumerate(issues_list, 1):
1589
- if issue.file and not is_file_processable(
1590
- issue.file,
1591
- allowed_suffixes=[".py"],
1592
- excluded_suffixes=[],
1593
- allowed_prefixes=[],
1594
- excluded_prefixes=["test_"],
1595
- ):
1631
+ if _should_skip_file(issue.file):
1596
1632
  console.print(
1597
1633
  f"[dim]Skipping {issue.file} "
1598
1634
  f"(only .py files excluding test_ are processed)[/dim]"
@@ -1608,6 +1644,7 @@ async def _process_issues_for_rule(
1608
1644
  rule_key=rule_key,
1609
1645
  md_file_path=md_file_path,
1610
1646
  tmp_path=tmp_path,
1647
+ check_tmp_path=check_tmp_path,
1611
1648
  )
1612
1649
 
1613
1650
  if not await _should_continue_to_next_issue(
@@ -1627,6 +1664,7 @@ async def _process_single_fix(
1627
1664
  rule_key: str,
1628
1665
  tmp_path: Path,
1629
1666
  md_file_path: Optional[Path] = None,
1667
+ check_tmp_path: bool = True,
1630
1668
  ) -> None:
1631
1669
  """
1632
1670
  Generate and handle a single fix.
@@ -1639,6 +1677,7 @@ async def _process_single_fix(
1639
1677
  str(tmp_path),
1640
1678
  rule_key,
1641
1679
  md_file_path,
1680
+ check_tmp_path=check_tmp_path,
1642
1681
  )
1643
1682
 
1644
1683
  if fixes:
@@ -1655,6 +1694,7 @@ async def _process_security_issues(
1655
1694
  fix_params: Dict[str, Any],
1656
1695
  md_file_path: Path,
1657
1696
  tmp_path: Path,
1697
+ check_tmp_path: bool = True,
1658
1698
  ) -> None:
1659
1699
  """
1660
1700
  Process security issues grouped by file.
@@ -1666,13 +1706,7 @@ async def _process_security_issues(
1666
1706
  for idx, (file_key, issues) in enumerate(issues_by_file.items(), 1):
1667
1707
  console.print(f"\n[blue]Processing ({idx}/{total_files}): {file_key}[/blue]")
1668
1708
  for idx_new, issue in enumerate(issues, 1):
1669
- if issue.file and not is_file_processable(
1670
- issue.file,
1671
- allowed_suffixes=[".py"],
1672
- excluded_suffixes=[],
1673
- allowed_prefixes=[],
1674
- excluded_prefixes=["test_"],
1675
- ):
1709
+ if _should_skip_file(issue.file):
1676
1710
  console.print(
1677
1711
  f"[dim]Skipping {issue.file} "
1678
1712
  f"(only .py files excluding test_ are processed)[/dim]"
@@ -1688,6 +1722,7 @@ async def _process_security_issues(
1688
1722
  rule_key=file_key,
1689
1723
  md_file_path=md_file_path,
1690
1724
  tmp_path=tmp_path,
1725
+ check_tmp_path=check_tmp_path,
1691
1726
  )
1692
1727
 
1693
1728
  if not await _should_continue_to_next_issue(idx, total_files):
@@ -1732,6 +1767,7 @@ async def _generate_fix_for_file(
1732
1767
  tmp_path: str,
1733
1768
  rule_name: Optional[str] = None,
1734
1769
  md_file_path: Optional[Path] = None,
1770
+ check_tmp_path: bool = True,
1735
1771
  ) -> Optional[List[FixSuggestion]]:
1736
1772
  """Generate fix for a file."""
1737
1773
  with show_progress("Generating fixes...", total=len(issues)) as (progress, task):
@@ -1746,6 +1782,7 @@ async def _generate_fix_for_file(
1746
1782
  project_path=Path(str(auth_config.project_path)),
1747
1783
  tmp_path=Path(tmp_path),
1748
1784
  file_md=file_md_str,
1785
+ check_tmp_path=check_tmp_path,
1749
1786
  )
1750
1787
  return result
1751
1788
 
@@ -1875,6 +1912,7 @@ def _fetch_issues_by_type(
1875
1912
  branch=branch or "",
1876
1913
  pull_request=pr_number,
1877
1914
  max_issues=fix_params["max_fixes"],
1915
+ language=PYTHON,
1878
1916
  )
1879
1917
  else:
1880
1918
  with show_progress(constant.FETCHING_ISSUES) as (progress, task):
@@ -1887,6 +1925,7 @@ def _fetch_issues_by_type(
1887
1925
  types_list=fix_params["types_list"],
1888
1926
  rules_excluded=fix_params["exclude_rules"],
1889
1927
  group_by="rules",
1928
+ languages=[PYTHON.sonar_language_key, IPYTHON.sonar_language_key],
1890
1929
  )
1891
1930
 
1892
1931