patchi 0.7.5__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 (496) hide show
  1. patchi/__init__.py +1 -0
  2. patchi/__main__.py +13 -0
  3. patchi/cli/__init__.py +0 -0
  4. patchi/cli/command_families.py +235 -0
  5. patchi/cli/commands/__init__.py +0 -0
  6. patchi/cli/commands/access_cmd.py +65 -0
  7. patchi/cli/commands/agent_stats_cmd.py +273 -0
  8. patchi/cli/commands/agents_cmd.py +187 -0
  9. patchi/cli/commands/ai_cmd.py +308 -0
  10. patchi/cli/commands/assure_cmd.py +538 -0
  11. patchi/cli/commands/audit_cmd.py +286 -0
  12. patchi/cli/commands/auto_cmd.py +110 -0
  13. patchi/cli/commands/blame_cmd.py +53 -0
  14. patchi/cli/commands/chains_cmd.py +327 -0
  15. patchi/cli/commands/charter_cmd.py +173 -0
  16. patchi/cli/commands/chat_cmd.py +597 -0
  17. patchi/cli/commands/check_cmd.py +232 -0
  18. patchi/cli/commands/cleanup_cmd.py +328 -0
  19. patchi/cli/commands/cockpit_cmd.py +208 -0
  20. patchi/cli/commands/commands_cmd.py +160 -0
  21. patchi/cli/commands/cross_repo_cmd.py +113 -0
  22. patchi/cli/commands/deps_cmd.py +110 -0
  23. patchi/cli/commands/dev_check_cmd.py +342 -0
  24. patchi/cli/commands/dev_cmd.py +445 -0
  25. patchi/cli/commands/doctor_cmd.py +457 -0
  26. patchi/cli/commands/findings_cmd.py +187 -0
  27. patchi/cli/commands/fix_cmd.py +648 -0
  28. patchi/cli/commands/fix_review_cmd.py +273 -0
  29. patchi/cli/commands/heatmap_cmd.py +62 -0
  30. patchi/cli/commands/help_cmd.py +159 -0
  31. patchi/cli/commands/hosted_cmd.py +980 -0
  32. patchi/cli/commands/init.py +456 -0
  33. patchi/cli/commands/key_cmd.py +571 -0
  34. patchi/cli/commands/learn_cmd.py +140 -0
  35. patchi/cli/commands/link_cmd.py +104 -0
  36. patchi/cli/commands/log_cmd.py +57 -0
  37. patchi/cli/commands/memory_cmd.py +215 -0
  38. patchi/cli/commands/model_cmd.py +207 -0
  39. patchi/cli/commands/notify_cmd.py +213 -0
  40. patchi/cli/commands/notify_prompts.py +29 -0
  41. patchi/cli/commands/patch_cmd.py +231 -0
  42. patchi/cli/commands/plan_cmd.py +128 -0
  43. patchi/cli/commands/plugins_cmd.py +307 -0
  44. patchi/cli/commands/queue_cmd.py +152 -0
  45. patchi/cli/commands/quick_cmd.py +159 -0
  46. patchi/cli/commands/ready_cmd.py +292 -0
  47. patchi/cli/commands/report_cmd.py +416 -0
  48. patchi/cli/commands/restrict_cmd.py +127 -0
  49. patchi/cli/commands/review_cmd.py +225 -0
  50. patchi/cli/commands/rules_cmd.py +233 -0
  51. patchi/cli/commands/scan_cmd.py +1908 -0
  52. patchi/cli/commands/settings_cmd.py +45 -0
  53. patchi/cli/commands/status_cmd.py +679 -0
  54. patchi/cli/commands/test_cmd.py +1052 -0
  55. patchi/cli/commands/trend_cmd.py +296 -0
  56. patchi/cli/commands/undo_cmd.py +235 -0
  57. patchi/cli/commands/update_cmd.py +345 -0
  58. patchi/cli/commands/verify_cmd.py +56 -0
  59. patchi/cli/commands/vr_cmd.py +440 -0
  60. patchi/cli/commands/watch_cmd.py +304 -0
  61. patchi/cli/commands/web_cmd.py +114 -0
  62. patchi/cli/console.py +101 -0
  63. patchi/cli/display/__init__.py +1 -0
  64. patchi/cli/display/live_audit.py +269 -0
  65. patchi/cli/display/live_progress.py +169 -0
  66. patchi/cli/framework.py +241 -0
  67. patchi/cli/logo.py +128 -0
  68. patchi/cli/main.py +222 -0
  69. patchi/cli/registry.py +1171 -0
  70. patchi/cli/style.py +68 -0
  71. patchi/cli/themes.py +187 -0
  72. patchi/cli/ux.py +444 -0
  73. patchi/core/__init__.py +0 -0
  74. patchi/core/agents/__init__.py +0 -0
  75. patchi/core/agents/api_fuzzer_agent.py +141 -0
  76. patchi/core/agents/attack_agent.py +242 -0
  77. patchi/core/agents/base.py +1012 -0
  78. patchi/core/agents/bug_predictor_agent.py +58 -0
  79. patchi/core/agents/build_tool_validator.py +150 -0
  80. patchi/core/agents/cache.py +192 -0
  81. patchi/core/agents/chaos_agent.py +133 -0
  82. patchi/core/agents/cicd_generator.py +240 -0
  83. patchi/core/agents/comment_scanner.py +280 -0
  84. patchi/core/agents/console_logging_agent.py +156 -0
  85. patchi/core/agents/coordinator.py +858 -0
  86. patchi/core/agents/core_scanner.py +269 -0
  87. patchi/core/agents/coverage_prioritizer.py +181 -0
  88. patchi/core/agents/dead_code_hygiene.py +296 -0
  89. patchi/core/agents/dead_code_scanner.py +692 -0
  90. patchi/core/agents/dependency_scanner.py +725 -0
  91. patchi/core/agents/doc_claim_agent.py +573 -0
  92. patchi/core/agents/duplicate_scanner.py +568 -0
  93. patchi/core/agents/env_scanner.py +410 -0
  94. patchi/core/agents/feature_flag_agent.py +89 -0
  95. patchi/core/agents/frontend_framework_agent.py +133 -0
  96. patchi/core/agents/goroutine_agent.py +82 -0
  97. patchi/core/agents/governor.py +1972 -0
  98. patchi/core/agents/i18n_agent.py +95 -0
  99. patchi/core/agents/license_compliance.py +247 -0
  100. patchi/core/agents/linking_agent.py +251 -0
  101. patchi/core/agents/memory_profiler_agent.py +102 -0
  102. patchi/core/agents/modernization_agent.py +84 -0
  103. patchi/core/agents/mutation_agent.py +174 -0
  104. patchi/core/agents/nl_query_agent.py +74 -0
  105. patchi/core/agents/promise_rejection_agent.py +100 -0
  106. patchi/core/agents/refactoring_agent.py +286 -0
  107. patchi/core/agents/resource_leak_agent.py +91 -0
  108. patchi/core/agents/route_graph_scanner.py +951 -0
  109. patchi/core/agents/run_agent.py +106 -0
  110. patchi/core/agents/rust_unwrap_agent.py +100 -0
  111. patchi/core/agents/sbom_generator.py +284 -0
  112. patchi/core/agents/scanners.py +67 -0
  113. patchi/core/agents/side/__init__.py +1 -0
  114. patchi/core/agents/side/build_agent.py +65 -0
  115. patchi/core/agents/side/format_agent.py +50 -0
  116. patchi/core/agents/side/install_agent.py +63 -0
  117. patchi/core/agents/side_file_scanner.py +549 -0
  118. patchi/core/agents/snapshot_drift_detector.py +146 -0
  119. patchi/core/agents/spa_route_inventory.py +203 -0
  120. patchi/core/agents/test_scanner.py +442 -0
  121. patchi/core/agents/tool_health.py +109 -0
  122. patchi/core/agents/tool_runner.py +162 -0
  123. patchi/core/agents/type_scanner.py +168 -0
  124. patchi/core/agents/ui_scanner.py +558 -0
  125. patchi/core/ai/__init__.py +20 -0
  126. patchi/core/ai/agent_audit.py +153 -0
  127. patchi/core/ai/agent_profiler.py +277 -0
  128. patchi/core/ai/client.py +499 -0
  129. patchi/core/ai/cost_tracker.py +107 -0
  130. patchi/core/ai/model_router.py +318 -0
  131. patchi/core/ai/orchestrator.py +625 -0
  132. patchi/core/ai/prompts.py +681 -0
  133. patchi/core/ai/smart.py +302 -0
  134. patchi/core/ai/tool_executor.py +24 -0
  135. patchi/core/ai/tools/__init__.py +10 -0
  136. patchi/core/ai/tools/executor.py +621 -0
  137. patchi/core/ai/tools/realize.py +1160 -0
  138. patchi/core/ai/tools/registry.py +1577 -0
  139. patchi/core/assurance/__init__.py +33 -0
  140. patchi/core/assurance/builder.py +211 -0
  141. patchi/core/assurance/graph.py +342 -0
  142. patchi/core/assurance/invariants.py +168 -0
  143. patchi/core/atomic.py +61 -0
  144. patchi/core/attackers/__init__.py +17 -0
  145. patchi/core/attackers/api_abuse.py +89 -0
  146. patchi/core/attackers/auth_bypass.py +73 -0
  147. patchi/core/attackers/base.py +91 -0
  148. patchi/core/attackers/business_logic.py +88 -0
  149. patchi/core/attackers/planner.py +108 -0
  150. patchi/core/attackers/priv_escalation.py +88 -0
  151. patchi/core/attackers/recon.py +87 -0
  152. patchi/core/brain/__init__.py +0 -0
  153. patchi/core/brain/ast_utils/__init__.py +81 -0
  154. patchi/core/brain/ast_utils/assignments.py +219 -0
  155. patchi/core/brain/ast_utils/calls.py +139 -0
  156. patchi/core/brain/ast_utils/config.py +132 -0
  157. patchi/core/brain/ast_utils/control_flow.py +74 -0
  158. patchi/core/brain/ast_utils/dead_symbols.py +205 -0
  159. patchi/core/brain/ast_utils/decorators.py +108 -0
  160. patchi/core/brain/ast_utils/helpers.py +144 -0
  161. patchi/core/brain/ast_utils/imports.py +169 -0
  162. patchi/core/brain/ast_utils/scan.py +143 -0
  163. patchi/core/brain/ast_utils/taint.py +310 -0
  164. patchi/core/brain/audit.py +379 -0
  165. patchi/core/brain/baseline.py +195 -0
  166. patchi/core/brain/blast_radius.py +312 -0
  167. patchi/core/brain/body_tags.py +144 -0
  168. patchi/core/brain/brain.py +1161 -0
  169. patchi/core/brain/brain_context.py +393 -0
  170. patchi/core/brain/brain_watcher.py +340 -0
  171. patchi/core/brain/charter.py +473 -0
  172. patchi/core/brain/classifier.py +214 -0
  173. patchi/core/brain/cockpit.py +350 -0
  174. patchi/core/brain/code_query.py +710 -0
  175. patchi/core/brain/contract.py +889 -0
  176. patchi/core/brain/contract_diff.py +155 -0
  177. patchi/core/brain/council.py +800 -0
  178. patchi/core/brain/cross_repo.py +104 -0
  179. patchi/core/brain/dead_code_tools.py +157 -0
  180. patchi/core/brain/doc_validator.py +197 -0
  181. patchi/core/brain/domain_activator.py +2183 -0
  182. patchi/core/brain/enriched_context.py +220 -0
  183. patchi/core/brain/file_corpus.py +220 -0
  184. patchi/core/brain/framework.py +953 -0
  185. patchi/core/brain/freshness.py +273 -0
  186. patchi/core/brain/git_aware.py +206 -0
  187. patchi/core/brain/heatmap.py +104 -0
  188. patchi/core/brain/ignore_parser.py +153 -0
  189. patchi/core/brain/import_graph.py +486 -0
  190. patchi/core/brain/languages.py +397 -0
  191. patchi/core/brain/layered_brain.py +419 -0
  192. patchi/core/brain/learning.py +229 -0
  193. patchi/core/brain/orphaned_endpoints.py +313 -0
  194. patchi/core/brain/personas/__init__.py +648 -0
  195. patchi/core/brain/personas/bad_user.py +165 -0
  196. patchi/core/brain/personas/base.py +349 -0
  197. patchi/core/brain/proactive.py +807 -0
  198. patchi/core/brain/project_context.py +121 -0
  199. patchi/core/brain/project_reader.py +521 -0
  200. patchi/core/brain/reasoning.py +269 -0
  201. patchi/core/brain/route_detector/__init__.py +15 -0
  202. patchi/core/brain/route_detector/base.py +31 -0
  203. patchi/core/brain/route_detector/csharp.py +209 -0
  204. patchi/core/brain/route_detector/file_based.py +81 -0
  205. patchi/core/brain/route_detector/go.py +152 -0
  206. patchi/core/brain/route_detector/java.py +195 -0
  207. patchi/core/brain/route_detector/javascript.py +197 -0
  208. patchi/core/brain/route_detector/php.py +158 -0
  209. patchi/core/brain/route_detector/python.py +193 -0
  210. patchi/core/brain/route_detector/registry.py +25 -0
  211. patchi/core/brain/route_detector/ruby.py +134 -0
  212. patchi/core/brain/route_detector/rust.py +216 -0
  213. patchi/core/brain/route_detector/swift.py +118 -0
  214. patchi/core/brain/route_mapper.py +265 -0
  215. patchi/core/brain/scanner.py +2136 -0
  216. patchi/core/brain/secrets.py +280 -0
  217. patchi/core/brain/symbol_graph.py +1955 -0
  218. patchi/core/brain/team.py +28 -0
  219. patchi/core/brain/trace_log.py +132 -0
  220. patchi/core/brain/type_checker/__init__.py +65 -0
  221. patchi/core/brain/type_checker/base.py +60 -0
  222. patchi/core/brain/type_checker/csharp.py +59 -0
  223. patchi/core/brain/type_checker/dart.py +59 -0
  224. patchi/core/brain/type_checker/go.py +81 -0
  225. patchi/core/brain/type_checker/java.py +75 -0
  226. patchi/core/brain/type_checker/kotlin.py +75 -0
  227. patchi/core/brain/type_checker/php.py +88 -0
  228. patchi/core/brain/type_checker/python.py +87 -0
  229. patchi/core/brain/type_checker/rust.py +81 -0
  230. patchi/core/brain/type_checker/swift.py +59 -0
  231. patchi/core/brain/type_checker/typescript.py +298 -0
  232. patchi/core/brain/understander.py +130 -0
  233. patchi/core/brain/verify.py +379 -0
  234. patchi/core/campaigns/__init__.py +14 -0
  235. patchi/core/campaigns/base.py +62 -0
  236. patchi/core/campaigns/data_flow_abuse.py +153 -0
  237. patchi/core/campaigns/orchestrator.py +140 -0
  238. patchi/core/campaigns/state_transitions.py +178 -0
  239. patchi/core/ci_bundle.py +104 -0
  240. patchi/core/config.py +311 -0
  241. patchi/core/constants.py +245 -0
  242. patchi/core/debug/__init__.py +25 -0
  243. patchi/core/debug/adapters/__init__.py +11 -0
  244. patchi/core/debug/adapters/codelldb.py +379 -0
  245. patchi/core/debug/adapters/node.py +222 -0
  246. patchi/core/debug/adapters/powershell.py +210 -0
  247. patchi/core/debug/adapters/python.py +244 -0
  248. patchi/core/debug/capture.py +210 -0
  249. patchi/core/debug/dap_client.py +268 -0
  250. patchi/core/detector/__init__.py +0 -0
  251. patchi/core/detector/audit.py +194 -0
  252. patchi/core/detector/bus.py +226 -0
  253. patchi/core/detector/dispatcher.py +275 -0
  254. patchi/core/detector/event.py +254 -0
  255. patchi/core/detector/sigma_engine.py +378 -0
  256. patchi/core/detector/triage.py +289 -0
  257. patchi/core/export/sarif.py +113 -0
  258. patchi/core/fix/__init__.py +17 -0
  259. patchi/core/fix/applier.py +574 -0
  260. patchi/core/fix/base.py +510 -0
  261. patchi/core/fix/code_fixer.py +169 -0
  262. patchi/core/fix/dead_code_remover.py +131 -0
  263. patchi/core/fix/fix_agents.py +775 -0
  264. patchi/core/fix/patch.py +444 -0
  265. patchi/core/fix/risk_gate.py +383 -0
  266. patchi/core/fix/security_fixer.py +93 -0
  267. patchi/core/fix/verify_loop.py +204 -0
  268. patchi/core/fuzz/__init__.py +8 -0
  269. patchi/core/fuzz/corpus.py +137 -0
  270. patchi/core/fuzz/input_fuzzer.py +256 -0
  271. patchi/core/fuzz/sequence_fuzzer.py +162 -0
  272. patchi/core/fuzz/state_fuzzer.py +236 -0
  273. patchi/core/health.py +407 -0
  274. patchi/core/hosted/__init__.py +1 -0
  275. patchi/core/hosted/anomaly.py +298 -0
  276. patchi/core/hosted/audit_log.py +102 -0
  277. patchi/core/hosted/compliance_report.py +148 -0
  278. patchi/core/hosted/ip_reputation.py +169 -0
  279. patchi/core/hosted/log_parsers.py +251 -0
  280. patchi/core/hosted/tokens.py +127 -0
  281. patchi/core/hosted/watchlist.py +164 -0
  282. patchi/core/hosted/webhooks.py +156 -0
  283. patchi/core/memory.py +437 -0
  284. patchi/core/notifications/__init__.py +18 -0
  285. patchi/core/notifications/channels.py +192 -0
  286. patchi/core/notifications/digest.py +141 -0
  287. patchi/core/notifications/escalation.py +158 -0
  288. patchi/core/notifications/notifier.py +182 -0
  289. patchi/core/notifications/quiet_hours.py +69 -0
  290. patchi/core/plugins/__init__.py +48 -0
  291. patchi/core/plugins/analyzer.py +326 -0
  292. patchi/core/plugins/analyzers/__init__.py +6 -0
  293. patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
  294. patchi/core/plugins/analyzers/secret_scanner.py +158 -0
  295. patchi/core/plugins/analyzers/todo_scanner.py +115 -0
  296. patchi/core/plugins/registry.py +335 -0
  297. patchi/core/queue.py +375 -0
  298. patchi/core/reliability/__init__.py +13 -0
  299. patchi/core/reliability/chaos.py +163 -0
  300. patchi/core/reliability/fault_injector.py +172 -0
  301. patchi/core/reliability/idempotency.py +128 -0
  302. patchi/core/reliability/recovery.py +147 -0
  303. patchi/core/runtime/__init__.py +5 -0
  304. patchi/core/runtime/tracer.py +230 -0
  305. patchi/core/scan_bus.py +156 -0
  306. patchi/core/security/__init__.py +1 -0
  307. patchi/core/security/ai_validator.py +462 -0
  308. patchi/core/security/app_mapper.py +216 -0
  309. patchi/core/security/app_profile.py +464 -0
  310. patchi/core/security/attack_feedback.py +141 -0
  311. patchi/core/security/attack_scenarios/__init__.py +1 -0
  312. patchi/core/security/attack_tree.py +176 -0
  313. patchi/core/security/auth_audit_agent.py +344 -0
  314. patchi/core/security/authz_agent.py +602 -0
  315. patchi/core/security/auto_fix_proactive.py +620 -0
  316. patchi/core/security/auto_fixer.py +660 -0
  317. patchi/core/security/auto_ticket.py +53 -0
  318. patchi/core/security/bandit_agent.py +152 -0
  319. patchi/core/security/blast_radius.py +226 -0
  320. patchi/core/security/browser_tester.py +333 -0
  321. patchi/core/security/business_logic_agent.py +263 -0
  322. patchi/core/security/catch_block_auditor.py +395 -0
  323. patchi/core/security/cdn_cache_agent.py +357 -0
  324. patchi/core/security/chain_analyzer.py +321 -0
  325. patchi/core/security/chain_to_assurance.py +168 -0
  326. patchi/core/security/charter.py +510 -0
  327. patchi/core/security/cloud_waf_detector.py +295 -0
  328. patchi/core/security/codeql_agent.py +233 -0
  329. patchi/core/security/compliance_agent.py +348 -0
  330. patchi/core/security/confidence_gate.py +423 -0
  331. patchi/core/security/container_scanner.py +282 -0
  332. patchi/core/security/crypto_agent.py +378 -0
  333. patchi/core/security/cve_monitor.py +262 -0
  334. patchi/core/security/dast_agent.py +865 -0
  335. patchi/core/security/defectdojo.py +238 -0
  336. patchi/core/security/defenders/__init__.py +75 -0
  337. patchi/core/security/defenders/auth_middleware.py +115 -0
  338. patchi/core/security/defenders/base.py +97 -0
  339. patchi/core/security/defenders/block_ip.py +71 -0
  340. patchi/core/security/defenders/block_ws_origin.py +36 -0
  341. patchi/core/security/defenders/crypto_fix.py +118 -0
  342. patchi/core/security/defenders/enforce_rate_limit.py +108 -0
  343. patchi/core/security/defenders/fix_code.py +93 -0
  344. patchi/core/security/defenders/invalidate_session.py +40 -0
  345. patchi/core/security/defenders/patch_config.py +70 -0
  346. patchi/core/security/defenders/rotate_secret.py +54 -0
  347. patchi/core/security/defenders/suspend_account.py +41 -0
  348. patchi/core/security/defenders/update_dependency.py +160 -0
  349. patchi/core/security/defense_layer.py +217 -0
  350. patchi/core/security/dependency_vulnerability_agent.py +616 -0
  351. patchi/core/security/detection_pipeline.py +289 -0
  352. patchi/core/security/dns_security_agent.py +497 -0
  353. patchi/core/security/domain_activator_v2.py +913 -0
  354. patchi/core/security/domain_loader.py +1004 -0
  355. patchi/core/security/domains/__init__.py +0 -0
  356. patchi/core/security/email_authentication_agent.py +417 -0
  357. patchi/core/security/env_var_validator.py +304 -0
  358. patchi/core/security/evidence.py +100 -0
  359. patchi/core/security/falco_runtime_agent.py +387 -0
  360. patchi/core/security/fix-playbooks/__init__.py +0 -0
  361. patchi/core/security/gated_finding.py +85 -0
  362. patchi/core/security/git_diff_activator.py +367 -0
  363. patchi/core/security/governance.py +208 -0
  364. patchi/core/security/gradual_gate.py +34 -0
  365. patchi/core/security/history.py +315 -0
  366. patchi/core/security/iac_scanner.py +250 -0
  367. patchi/core/security/ignore_expiry.py +111 -0
  368. patchi/core/security/ignore_learner.py +416 -0
  369. patchi/core/security/injection_agent.py +319 -0
  370. patchi/core/security/insecure_randomness_agent.py +288 -0
  371. patchi/core/security/intent_analyzer.py +326 -0
  372. patchi/core/security/jwt_agent.py +453 -0
  373. patchi/core/security/kubernetes_agent.py +638 -0
  374. patchi/core/security/layer2_orchestrator.py +341 -0
  375. patchi/core/security/llm_security_agent.py +252 -0
  376. patchi/core/security/misconfig_agent.py +600 -0
  377. patchi/core/security/mobile_security_agent.py +474 -0
  378. patchi/core/security/network_agent.py +376 -0
  379. patchi/core/security/noise_filter.py +274 -0
  380. patchi/core/security/orchestrator.py +313 -0
  381. patchi/core/security/pattern_context.py +125 -0
  382. patchi/core/security/pattern_loader.py +101 -0
  383. patchi/core/security/pentest/__init__.py +4 -0
  384. patchi/core/security/pentest/base.py +58 -0
  385. patchi/core/security/pentest/dalfox_adapter.py +40 -0
  386. patchi/core/security/pentest/ffuf_adapter.py +48 -0
  387. patchi/core/security/pentest/nuclei_adapter.py +50 -0
  388. patchi/core/security/pentest/registry.py +80 -0
  389. patchi/core/security/pentest/shannon_adapter.py +107 -0
  390. patchi/core/security/pentest/sqlmap_adapter.py +56 -0
  391. patchi/core/security/pentest/zap_adapter.py +52 -0
  392. patchi/core/security/plan_auditor.py +276 -0
  393. patchi/core/security/policy_engine.py +272 -0
  394. patchi/core/security/prechecks.py +172 -0
  395. patchi/core/security/precommit_hook.py +247 -0
  396. patchi/core/security/privacy_agent.py +361 -0
  397. patchi/core/security/push_notification_agent.py +417 -0
  398. patchi/core/security/pysa_agent.py +143 -0
  399. patchi/core/security/reasoning.py +442 -0
  400. patchi/core/security/red_team_agent.py +184 -0
  401. patchi/core/security/red_team_engine.py +1142 -0
  402. patchi/core/security/remediation.py +360 -0
  403. patchi/core/security/request_interceptor.py +294 -0
  404. patchi/core/security/runtime_validator.py +245 -0
  405. patchi/core/security/saml_sso_agent.py +401 -0
  406. patchi/core/security/sast_agent.py +213 -0
  407. patchi/core/security/sast_gate.py +95 -0
  408. patchi/core/security/scheduler.py +287 -0
  409. patchi/core/security/secrets_runtime_agent.py +476 -0
  410. patchi/core/security/security_agents.py +175 -0
  411. patchi/core/security/security_config.py +551 -0
  412. patchi/core/security/security_probe.py +762 -0
  413. patchi/core/security/security_taint.py +1005 -0
  414. patchi/core/security/sensitive_data_agent.py +271 -0
  415. patchi/core/security/service_mesh_agent.py +412 -0
  416. patchi/core/security/session_management_agent.py +245 -0
  417. patchi/core/security/ssrf_agent.py +422 -0
  418. patchi/core/security/supply_chain.py +449 -0
  419. patchi/core/security/threat_model_generator.py +378 -0
  420. patchi/core/security/threat_model_updater.py +196 -0
  421. patchi/core/security/tool_adapters.py +135 -0
  422. patchi/core/security/tool_verify.py +173 -0
  423. patchi/core/security/websocket_security_agent.py +188 -0
  424. patchi/core/snapshot.py +242 -0
  425. patchi/core/tenant.py +386 -0
  426. patchi/core/testing/__init__.py +17 -0
  427. patchi/core/testing/_browser.py +147 -0
  428. patchi/core/testing/ads_agent.py +141 -0
  429. patchi/core/testing/api_contract_agent.py +450 -0
  430. patchi/core/testing/app_discovery_agent.py +641 -0
  431. patchi/core/testing/app_launcher.py +184 -0
  432. patchi/core/testing/browser_test_agent.py +516 -0
  433. patchi/core/testing/e2e_flow_agent.py +354 -0
  434. patchi/core/testing/flake_detector_agent.py +292 -0
  435. patchi/core/testing/gate.py +31 -0
  436. patchi/core/testing/live_test_runner.py +304 -0
  437. patchi/core/testing/live_v2/__init__.py +1 -0
  438. patchi/core/testing/live_v2/browser_pool.py +349 -0
  439. patchi/core/testing/live_v2/browser_test_runner.py +313 -0
  440. patchi/core/testing/live_v2/runner.py +626 -0
  441. patchi/core/testing/live_v2/screenshot_manager.py +474 -0
  442. patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
  443. patchi/core/testing/live_v2/video_recorder.py +303 -0
  444. patchi/core/testing/regression_agent.py +302 -0
  445. patchi/core/testing/security_test_agent.py +253 -0
  446. patchi/core/testing/stress_test_agent.py +203 -0
  447. patchi/core/testing/test_agents.py +50 -0
  448. patchi/core/testing/test_config.py +194 -0
  449. patchi/core/testing/ui_accessibility_agent.py +324 -0
  450. patchi/core/testing/ui_button_agent.py +294 -0
  451. patchi/core/testing/ui_layout_agent.py +330 -0
  452. patchi/core/testing/unit_test_agent.py +1149 -0
  453. patchi/core/testing/visual_regression_agent.py +260 -0
  454. patchi/web/__init__.py +1 -0
  455. patchi/web/api/__init__.py +1 -0
  456. patchi/web/api/brain_map.py +114 -0
  457. patchi/web/api/charts.py +47 -0
  458. patchi/web/api/chat.py +224 -0
  459. patchi/web/api/cicd.py +576 -0
  460. patchi/web/api/dast_evidence.py +49 -0
  461. patchi/web/api/dev_check.py +320 -0
  462. patchi/web/api/fix.py +134 -0
  463. patchi/web/api/guard.py +65 -0
  464. patchi/web/api/hosted.py +128 -0
  465. patchi/web/api/hosted_v2.py +234 -0
  466. patchi/web/api/live_testing.py +699 -0
  467. patchi/web/api/scan.py +582 -0
  468. patchi/web/api/smart.py +111 -0
  469. patchi/web/api/tenant.py +117 -0
  470. patchi/web/api/tools.py +85 -0
  471. patchi/web/api_legacy.py +2401 -0
  472. patchi/web/app.py +310 -0
  473. patchi/web/events.py +483 -0
  474. patchi/web/routes/__init__.py +1 -0
  475. patchi/web/routes/assurance.py +541 -0
  476. patchi/web/routes/brain.py +36 -0
  477. patchi/web/routes/charter.py +227 -0
  478. patchi/web/routes/chat.py +23 -0
  479. patchi/web/routes/dashboard.py +558 -0
  480. patchi/web/routes/dashboard_v2.py +402 -0
  481. patchi/web/routes/findings.py +330 -0
  482. patchi/web/routes/guard.py +35 -0
  483. patchi/web/routes/history.py +126 -0
  484. patchi/web/routes/landing.py +52 -0
  485. patchi/web/routes/live_testing.py +24 -0
  486. patchi/web/routes/review.py +57 -0
  487. patchi/web/routes/self_improvement.py +110 -0
  488. patchi/web/routes/settings.py +49 -0
  489. patchi/web/routes/tokens.py +29 -0
  490. patchi/web/spawn.py +139 -0
  491. patchi/web/ws.py +141 -0
  492. patchi-0.7.5.dist-info/METADATA +315 -0
  493. patchi-0.7.5.dist-info/RECORD +496 -0
  494. patchi-0.7.5.dist-info/WHEEL +5 -0
  495. patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
  496. patchi-0.7.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,69 @@
1
+ """
2
+ Quiet hours enforcement for Patchi notifications.
3
+
4
+ Quiet hours block non-urgent alerts during the configured window.
5
+ CRITICAL and HIGH severity always bypass quiet hours.
6
+ MEDIUM, LOW, INFO are suppressed during the window.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import datetime, time
12
+
13
+ # Severities that always fire regardless of quiet hours
14
+ _BYPASS_SEVERITIES: frozenset[str] = frozenset({"critical", "high"})
15
+
16
+
17
+ def _parse_time(value: str) -> time:
18
+ """Parse HH:MM string to a time object. Raises ValueError on bad format."""
19
+ try:
20
+ h, m = value.split(":")
21
+ return time(int(h), int(m))
22
+ except (ValueError, AttributeError) as exc:
23
+ raise ValueError(f"Invalid time format '{value}' — expected HH:MM") from exc
24
+
25
+
26
+ def is_quiet_now(quiet_config: dict) -> bool:
27
+ """
28
+ Return True if the current moment falls inside the configured quiet window.
29
+
30
+ quiet_config shape (from config.json):
31
+ {
32
+ "enabled": bool,
33
+ "start": "22:00",
34
+ "end": "08:00",
35
+ "timezone": "UTC" # currently ignored — uses system local time
36
+ }
37
+
38
+ Handles overnight windows (e.g. 22:00 → 08:00 wraps past midnight).
39
+ """
40
+ if not quiet_config.get("enabled", False):
41
+ return False
42
+
43
+ try:
44
+ start = _parse_time(quiet_config.get("start", "22:00"))
45
+ end = _parse_time(quiet_config.get("end", "08:00"))
46
+ except ValueError:
47
+ # Misconfigured quiet hours — treat as off to avoid silent notification loss
48
+ return False
49
+
50
+ now = datetime.now().time()
51
+
52
+ if start <= end:
53
+ # Same-day window: e.g. 09:00 → 17:00
54
+ return start <= now < end
55
+ else:
56
+ # Overnight window: e.g. 22:00 → 08:00
57
+ return now >= start or now < end
58
+
59
+
60
+ def should_suppress(severity: str, quiet_config: dict) -> bool:
61
+ """
62
+ Return True if this alert should be suppressed due to quiet hours.
63
+
64
+ CRITICAL and HIGH always fire.
65
+ MEDIUM/LOW/INFO are suppressed during the quiet window.
66
+ """
67
+ if severity in _BYPASS_SEVERITIES:
68
+ return False
69
+ return is_quiet_now(quiet_config)
@@ -0,0 +1,48 @@
1
+ """
2
+ Plugin system for Patchi.
3
+
4
+ Provides a formal contract for analyzers, tools, and agents that can be
5
+ added without touching core code. New plugins are discovered automatically
6
+ from the plugins directory.
7
+
8
+ Usage:
9
+ # To add a new analyzer, create a file in patchi/core/plugins/analyzers/
10
+ # and define a class that inherits from Analyzer:
11
+
12
+ from patchi.core.plugins.analyzer import Analyzer, AnalyzerResult
13
+
14
+ class MyAnalyzer(Analyzer):
15
+ name = "my-analyzer"
16
+ version = "1.0.0"
17
+ description = "Does something useful"
18
+
19
+ def analyze(self, context) -> AnalyzerResult:
20
+ # Your analysis logic here
21
+ return AnalyzerResult(findings=[...])
22
+
23
+ # That's it! The plugin is automatically discovered and registered.
24
+ """
25
+
26
+ from patchi.core.plugins.analyzer import (
27
+ Analyzer,
28
+ AnalyzerContext,
29
+ AnalyzerResult,
30
+ Finding,
31
+ Severity,
32
+ )
33
+ from patchi.core.plugins.registry import (
34
+ PluginRegistry,
35
+ get_registry,
36
+ list_plugins,
37
+ )
38
+
39
+ __all__ = [
40
+ "Analyzer",
41
+ "AnalyzerContext",
42
+ "AnalyzerResult",
43
+ "Finding",
44
+ "Severity",
45
+ "PluginRegistry",
46
+ "get_registry",
47
+ "list_plugins",
48
+ ]
@@ -0,0 +1,326 @@
1
+ """
2
+ Formal Analyzer plugin interface for Patchi.
3
+
4
+ This module defines the contract that all analyzers must follow:
5
+ - Input: AnalyzerContext (AST, graph, project metadata)
6
+ - Output: AnalyzerResult (findings with stable IDs)
7
+
8
+ Stable Finding IDs:
9
+ Every finding has a deterministic ID based on:
10
+ sha256(f"{analyzer_name}:{file}:{line}:{type}:{message}")[:16]
11
+
12
+ This means:
13
+ 1. The same issue always has the same ID (deterministic)
14
+ 2. Different analyzers produce different IDs (namespaced)
15
+ 3. Moving a line changes the ID (line-sensitive)
16
+ 4. IDs are short (16 chars) for readability
17
+
18
+ Adding a new analyzer:
19
+ 1. Create a file in patchi/core/plugins/analyzers/
20
+ 2. Define a class that inherits from Analyzer
21
+ 3. Implement analyze(context) -> AnalyzerResult
22
+ 4. That's it! Auto-discovered and registered.
23
+
24
+ Example:
25
+ from patchi.core.plugins.analyzer import Analyzer, AnalyzerResult, Finding
26
+
27
+ class SecretScanner(Analyzer):
28
+ name = "secret-scanner"
29
+ version = "1.0.0"
30
+ description = "Scans for hardcoded secrets"
31
+
32
+ def analyze(self, context) -> AnalyzerResult:
33
+ findings = []
34
+ for file in context.files:
35
+ if "password" in file.content.lower():
36
+ findings.append(Finding(
37
+ file=file.path,
38
+ line=find_line(file.content, "password"),
39
+ type="hardcoded-secret",
40
+ severity=Severity.CRITICAL,
41
+ message="Possible hardcoded secret",
42
+ ))
43
+ return AnalyzerResult(findings=findings)
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import hashlib
49
+ from abc import ABC, abstractmethod
50
+ from dataclasses import dataclass, field
51
+ from enum import StrEnum
52
+ from pathlib import Path
53
+ from typing import Any
54
+
55
+
56
+ class Severity(StrEnum):
57
+ """Finding severity levels."""
58
+ CRITICAL = "critical"
59
+ HIGH = "high"
60
+ MEDIUM = "medium"
61
+ LOW = "low"
62
+ INFO = "info"
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class FileNode:
67
+ """A file in the project with its content and metadata."""
68
+ path: str # relative to project root
69
+ content: str = ""
70
+ language: str = ""
71
+ size: int = 0
72
+ modified_at: str = ""
73
+ metadata: dict = field(default_factory=dict)
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class ASTNode:
78
+ """An AST node with position information."""
79
+ type: str # e.g. "function", "class", "import"
80
+ name: str = ""
81
+ start_line: int = 0
82
+ end_line: int = 0
83
+ start_col: int = 0
84
+ end_col: int = 0
85
+ children: tuple[ASTNode, ...] = ()
86
+ metadata: dict = field(default_factory=dict)
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class GraphEdge:
91
+ """An edge in the dependency/call graph."""
92
+ source: str # file or symbol
93
+ target: str # file or symbol
94
+ edge_type: str = "calls" # calls, imports, inherits, etc.
95
+ metadata: dict = field(default_factory=dict)
96
+
97
+
98
+ @dataclass
99
+ class AnalyzerContext:
100
+ """Input context for an analyzer.
101
+
102
+ Contains everything an analyzer needs:
103
+ - Project root and files
104
+ - AST trees per file (if parsed)
105
+ - Dependency/call graph
106
+ - Project metadata (from brain)
107
+ - Configuration
108
+ """
109
+ root: Path
110
+ files: list[FileNode] = field(default_factory=list)
111
+ asts: dict[str, list[ASTNode]] = field(default_factory=dict) # file -> AST nodes
112
+ graph_edges: list[GraphEdge] = field(default_factory=list)
113
+ project_name: str = ""
114
+ project_type: str = ""
115
+ tech_stack: list[str] = field(default_factory=list)
116
+ config: dict = field(default_factory=dict)
117
+ scope: list[str] = field(default_factory=list) # empty = all files
118
+ extra: dict = field(default_factory=dict) # analyzer-specific params
119
+
120
+
121
+ @dataclass
122
+ class Finding:
123
+ """A single finding from an analyzer.
124
+
125
+ Has a stable, deterministic ID based on the finding's properties.
126
+ """
127
+ file: str
128
+ line: int
129
+ type: str
130
+ severity: Severity
131
+ message: str
132
+ detail: str = ""
133
+ code_snippet: str = ""
134
+ suggestion: str = ""
135
+ cwe: str = ""
136
+ fix_hint: str = "" # hint for fix agents
137
+ confidence: float = 1.0 # 0.0 to 1.0
138
+ extra: dict = field(default_factory=dict)
139
+
140
+ # Computed stable ID
141
+ _id: str = field(default="", repr=False)
142
+
143
+ def __post_init__(self):
144
+ if not self._id:
145
+ self._id = self._compute_id()
146
+
147
+ def _compute_id(self) -> str:
148
+ """Compute deterministic ID from finding properties."""
149
+ # Include analyzer name in the hash for namespacing
150
+ # This is set by AnalyzerResult.__post_init__
151
+ analyzer = self.extra.get("_analyzer", "unknown")
152
+ raw = f"{analyzer}:{self.file}:{self.line}:{self.type}:{self.message}"
153
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]
154
+
155
+ @property
156
+ def id(self) -> str:
157
+ return self._id
158
+
159
+ def to_dict(self) -> dict:
160
+ return {
161
+ "id": self.id,
162
+ "file": self.file,
163
+ "line": self.line,
164
+ "type": self.type,
165
+ "severity": self.severity.value,
166
+ "message": self.message,
167
+ "detail": self.detail,
168
+ "code_snippet": self.code_snippet,
169
+ "suggestion": self.suggestion,
170
+ "cwe": self.cwe,
171
+ "fix_hint": self.fix_hint,
172
+ "confidence": self.confidence,
173
+ **self.extra,
174
+ }
175
+
176
+ @classmethod
177
+ def from_dict(cls, d: dict) -> Finding:
178
+ return cls(
179
+ file=d.get("file", ""),
180
+ line=d.get("line", 0),
181
+ type=d.get("type", ""),
182
+ severity=Severity(d.get("severity", "medium")),
183
+ message=d.get("message", ""),
184
+ detail=d.get("detail", ""),
185
+ code_snippet=d.get("code_snippet", ""),
186
+ suggestion=d.get("suggestion", ""),
187
+ cwe=d.get("cwe", ""),
188
+ fix_hint=d.get("fix_hint", ""),
189
+ confidence=d.get("confidence", 1.0),
190
+ extra={k: v for k, v in d.items() if k not in {
191
+ "id", "file", "line", "type", "severity", "message",
192
+ "detail", "code_snippet", "suggestion", "cwe", "fix_hint", "confidence",
193
+ }},
194
+ _id=d.get("id", ""),
195
+ )
196
+
197
+
198
+ @dataclass
199
+ class AnalyzerResult:
200
+ """Output from an analyzer.
201
+
202
+ Contains findings, metrics, and metadata about the analysis run.
203
+ """
204
+ findings: list[Finding] = field(default_factory=list)
205
+ metrics: dict[str, Any] = field(default_factory=dict)
206
+ errors: list[str] = field(default_factory=list)
207
+ warnings: list[str] = field(default_factory=list)
208
+ duration_ms: int = 0
209
+ files_analyzed: int = 0
210
+
211
+ def __post_init__(self):
212
+ # Set analyzer name on all findings for stable ID computation
213
+ pass
214
+
215
+ def to_dict(self) -> dict:
216
+ return {
217
+ "findings": [f.to_dict() for f in self.findings],
218
+ "metrics": self.metrics,
219
+ "errors": self.errors,
220
+ "warnings": self.warnings,
221
+ "duration_ms": self.duration_ms,
222
+ "files_analyzed": self.files_analyzed,
223
+ }
224
+
225
+
226
+ class Analyzer(ABC):
227
+ """Base class for all analyzers.
228
+
229
+ Subclasses must implement:
230
+ - name: unique identifier for this analyzer
231
+ - version: semantic version
232
+ - description: human-readable description
233
+ - analyze(context): perform analysis and return results
234
+
235
+ Optional overrides:
236
+ - supported_languages: restrict to specific languages
237
+ - supported_file_patterns: restrict to specific file patterns
238
+ - priority: execution order (lower = first)
239
+ - timeout: maximum execution time in seconds
240
+ """
241
+
242
+ # Required metadata
243
+ name: str = "unnamed-analyzer"
244
+ version: str = "0.0.0"
245
+ description: str = ""
246
+
247
+ # Optional configuration
248
+ supported_languages: list[str] = [] # empty = all languages
249
+ supported_file_patterns: list[str] = [] # empty = all files
250
+ priority: int = 100 # lower = runs first
251
+ timeout: int = 60 # seconds
252
+ enabled: bool = True
253
+
254
+ @abstractmethod
255
+ def analyze(self, context: AnalyzerContext) -> AnalyzerResult:
256
+ """Perform analysis and return results.
257
+
258
+ Args:
259
+ context: The analysis context containing files, ASTs, graph, etc.
260
+
261
+ Returns:
262
+ AnalyzerResult with findings and metadata.
263
+ """
264
+ ...
265
+
266
+ def should_analyze(self, file: FileNode) -> bool:
267
+ """Check if this analyzer should process the given file.
268
+
269
+ Override to implement custom filtering logic.
270
+ """
271
+ if self.supported_languages and file.language not in self.supported_languages:
272
+ return False
273
+ if self.supported_file_patterns:
274
+ import fnmatch
275
+ if not any(fnmatch.fnmatch(file.path, p) for p in self.supported_file_patterns):
276
+ return False
277
+ return True
278
+
279
+ def describe(self) -> dict:
280
+ """Return metadata for plugin discovery."""
281
+ return {
282
+ "name": self.name,
283
+ "version": self.version,
284
+ "description": self.description,
285
+ "supported_languages": self.supported_languages,
286
+ "supported_file_patterns": self.supported_file_patterns,
287
+ "priority": self.priority,
288
+ "timeout": self.timeout,
289
+ "enabled": self.enabled,
290
+ }
291
+
292
+
293
+ # ── Helper for creating findings ──────────────────────────────────────────────
294
+
295
+ def make_finding(
296
+ file: str,
297
+ line: int,
298
+ type: str,
299
+ severity: Severity,
300
+ message: str,
301
+ detail: str = "",
302
+ code_snippet: str = "",
303
+ suggestion: str = "",
304
+ cwe: str = "",
305
+ fix_hint: str = "",
306
+ confidence: float = 1.0,
307
+ **extra,
308
+ ) -> Finding:
309
+ """Create a Finding with the given properties.
310
+
311
+ This is a convenience function for creating findings in analyzers.
312
+ """
313
+ return Finding(
314
+ file=file,
315
+ line=line,
316
+ type=type,
317
+ severity=severity,
318
+ message=message,
319
+ detail=detail,
320
+ code_snippet=code_snippet,
321
+ suggestion=suggestion,
322
+ cwe=cwe,
323
+ fix_hint=fix_hint,
324
+ confidence=confidence,
325
+ extra=extra,
326
+ )
@@ -0,0 +1,6 @@
1
+ """
2
+ Built-in analyzers for Patchi.
3
+
4
+ These are the standard analyzers that ship with Patchi.
5
+ To add your own, create a file in this directory or in ~/.patchi/plugins/
6
+ """
@@ -0,0 +1,120 @@
1
+ """
2
+ Dead Code Analyzer — detects unused imports, variables, and functions.
3
+
4
+ This is a built-in analyzer that scans for:
5
+ - Unused imports
6
+ - Unused variables
7
+ - Unused functions
8
+ - Unreachable code
9
+
10
+ Example usage:
11
+ from patchi.core.plugins import get_registry, AnalyzerContext
12
+ from pathlib import Path
13
+
14
+ registry = get_registry()
15
+ context = AnalyzerContext(root=Path("."))
16
+ result = registry.run("dead-code-analyzer", context)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import ast
22
+
23
+ from patchi.core.plugins.analyzer import (
24
+ Analyzer,
25
+ AnalyzerContext,
26
+ AnalyzerResult,
27
+ FileNode,
28
+ Finding,
29
+ Severity,
30
+ make_finding,
31
+ )
32
+
33
+
34
+ class DeadCodeAnalyzer(Analyzer):
35
+ """Detects unused imports, variables, and functions."""
36
+
37
+ name = "dead-code-analyzer"
38
+ version = "1.0.0"
39
+ description = "Detects unused imports, variables, and functions"
40
+ priority = 50 # Run after secrets, before TODOs
41
+ supported_languages = ["python"] # Python only for now
42
+
43
+ def analyze(self, context: AnalyzerContext) -> AnalyzerResult:
44
+ findings: list[Finding] = []
45
+ files_scanned = 0
46
+
47
+ for file in context.files:
48
+ if not self.should_analyze(file):
49
+ continue
50
+
51
+ if not file.path.endswith(".py"):
52
+ continue
53
+
54
+ files_scanned += 1
55
+ file_findings = self._scan_python_file(file)
56
+ findings.extend(file_findings)
57
+
58
+ return AnalyzerResult(
59
+ findings=findings,
60
+ files_analyzed=files_scanned,
61
+ metrics={
62
+ "files_scanned": files_scanned,
63
+ "findings_count": len(findings),
64
+ },
65
+ )
66
+
67
+ def _scan_python_file(self, file: FileNode) -> list[Finding]:
68
+ """Scan a Python file for dead code."""
69
+ findings: list[Finding] = []
70
+
71
+ if not file.content:
72
+ return findings
73
+
74
+ try:
75
+ tree = ast.parse(file.content)
76
+ except SyntaxError:
77
+ return findings
78
+
79
+ # Collect all imports
80
+ imports = []
81
+ for node in ast.walk(tree):
82
+ if isinstance(node, ast.Import):
83
+ for alias in node.names:
84
+ name = alias.asname or alias.name
85
+ imports.append((name, node.lineno))
86
+ elif isinstance(node, ast.ImportFrom):
87
+ for alias in node.names:
88
+ name = alias.asname or alias.name
89
+ imports.append((name, node.lineno))
90
+
91
+ # Collect all names used in the file
92
+ used_names = set()
93
+ for node in ast.walk(tree):
94
+ if isinstance(node, ast.Name):
95
+ used_names.add(node.id)
96
+ elif isinstance(node, ast.Attribute):
97
+ # Handle attribute access like os.path
98
+ if isinstance(node.value, ast.Name):
99
+ used_names.add(node.value.id)
100
+
101
+ # Check for unused imports
102
+ for name, lineno in imports:
103
+ # Skip wildcard imports
104
+ if name == "*":
105
+ continue
106
+
107
+ # Check if name is used (excluding the import itself)
108
+ if name not in used_names:
109
+ findings.append(make_finding(
110
+ file=file.path,
111
+ line=lineno,
112
+ type="unused-import",
113
+ severity=Severity.LOW,
114
+ message=f"Unused import: {name}",
115
+ detail=f"Import '{name}' is defined but never used in this file",
116
+ suggestion=f"Remove unused import '{name}'",
117
+ confidence=0.9,
118
+ ))
119
+
120
+ return findings