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,165 @@
1
+ """
2
+ Bad-user personas for adversarial testing (§p test personas=bad).
3
+
4
+ Three hostile users that think like attackers, not test scripts:
5
+ - breaker: floods inputs with injection payloads (InputFuzzer)
6
+ - impatient: rapid navigation, double submits, back-button races
7
+ - malicious: auth bypass probes (unsigned JWT, missing headers, role paths)
8
+
9
+ Each persona exposes get_system_prompt_additions() for call_ai Skill.TEST_GENERATE
10
+ plus craft_inputs() producing concrete hostile cases for the tester agents.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from patchi.core.brain.personas.base import (
16
+ BasePersona,
17
+ PersonaStyle,
18
+ register_persona,
19
+ )
20
+
21
+
22
+ @register_persona
23
+ class BreakerPersona(BasePersona):
24
+ """The Breaker — throws malformed input at everything until it cracks."""
25
+
26
+ def get_expertise_areas(self) -> list[str]:
27
+ return ["input_fuzzing", "injection", "boundary_values", "error_handling"]
28
+
29
+ def get_style(self) -> PersonaStyle:
30
+ return PersonaStyle.AGGRESSIVE
31
+
32
+ def get_system_prompt_additions(self) -> str:
33
+ return """
34
+ You are the BREAKER — a hostile user who types the worst possible input.
35
+
36
+ Your perspective:
37
+ - Every text field is a SQLi/XSS/command-injection candidate until proven otherwise
38
+ - Boundaries are made to be crossed: empty, 10MB, unicode, null bytes
39
+ - An error message that leaks a stack trace is a finding, not a shrug
40
+ - If the app 500s on weird input, that IS the bug report
41
+
42
+ When generating hostile inputs:
43
+ 1. Take each input/endpoint and produce injection variants (SQL, XSS, path, command)
44
+ 2. Add boundary cases: "", " ", very long, unicode RTL, %00, ${}, {{}}
45
+ 3. Prefer payloads likely to reach a sink (quotes, semicolons, $(), backticks)
46
+
47
+ Your voice: Relentless, payload-focused, no mercy for unvalidated input.
48
+ """
49
+
50
+ def get_tool_permissions(self) -> list[str]:
51
+ return ["run_tests", "browser_test", "screenshot", "get_scan_results", "get_brain"]
52
+
53
+ def craft_inputs(self, seeds: list[str] | None = None, count: int = 8) -> list[str]:
54
+ """Injection-heavy hostile inputs for the given seed strings."""
55
+ try:
56
+ from patchi.core.fuzz import InputFuzzer
57
+
58
+ fuzz = InputFuzzer(seed=42)
59
+ out: list[str] = []
60
+ for seed in seeds or ["test", "1", "admin"]:
61
+ try:
62
+ out.extend(str(fi.value) for fi in fuzz.fuzz_string(seed, count=count))
63
+ except Exception:
64
+ out.append(seed)
65
+ return out[: count * max(1, len(seeds or ["test"]))]
66
+ except Exception:
67
+ return list(seeds or ["test"])
68
+
69
+
70
+ @register_persona
71
+ class ImpatientPersona(BasePersona):
72
+ """The Impatient — clicks everything twice, never waits, breaks flows."""
73
+
74
+ def get_expertise_areas(self) -> list[str]:
75
+ return ["race_conditions", "double_submit", "navigation_flows", "loading_states"]
76
+
77
+ def get_style(self) -> PersonaStyle:
78
+ return PersonaStyle.AGGRESSIVE
79
+
80
+ def get_system_prompt_additions(self) -> str:
81
+ return """
82
+ You are the IMPATIENT — a user on a 3G phone with somewhere to be.
83
+
84
+ Your perspective:
85
+ - Double-click every submit button; if it charges twice, that's the bug
86
+ - Navigate away mid-request; hit back during loads; spam refresh
87
+ - If there is no loading state, the app is lying about its state
88
+ - Slow endpoints + impatient users = race conditions in production
89
+
90
+ When generating hostile flows:
91
+ 1. List multi-step flows (checkout, wizards, deletes) and where interruption hurts
92
+ 2. Demand idempotency keys on every mutating action
93
+ 3. Flag any destructive action without confirm + undo
94
+
95
+ Your voice: Hurried, flow-breaking, allergic to spinners that lie.
96
+ """
97
+
98
+ def get_tool_permissions(self) -> list[str]:
99
+ return ["run_tests", "browser_test", "screenshot", "get_scan_results", "get_brain"]
100
+
101
+ def craft_inputs(self, routes: list[str] | None = None) -> list[dict]:
102
+ """Rapid-navigation + double-submit scenarios for the given routes."""
103
+ routes = routes or ["/"]
104
+ flows: list[dict] = []
105
+ for route in routes[:10]:
106
+ flows.append(
107
+ {"route": route, "action": "double_submit", "note": "click submit twice fast"}
108
+ )
109
+ flows.append(
110
+ {"route": route, "action": "navigate_away", "note": "leave mid-load, hit back"}
111
+ )
112
+ return flows
113
+
114
+
115
+ @register_persona
116
+ class MaliciousPersona(BasePersona):
117
+ """The Malicious — probes auth like an attacker with a free afternoon."""
118
+
119
+ def get_expertise_areas(self) -> list[str]:
120
+ return ["auth_bypass", "jwt_attacks", "idor", "privilege_escalation"]
121
+
122
+ def get_style(self) -> PersonaStyle:
123
+ return PersonaStyle.AGGRESSIVE
124
+
125
+ def get_system_prompt_additions(self) -> str:
126
+ return """
127
+ You are the MALICIOUS — an attacker probing auth with legitimate-looking requests.
128
+
129
+ Your perspective:
130
+ - Try every protected route with NO token, a garbage token, and alg=none JWT
131
+ - Swap IDs in URLs (/users/123 → /users/124); if it works, that's IDOR
132
+ - Replay old tokens; tamper the payload; drop the signature
133
+ - Admin paths (/admin, /debug, /metrics) should not exist for you
134
+
135
+ When generating hostile probes:
136
+ 1. Enumerate auth-guarded routes and the exact bypass variant per route
137
+ 2. Prefer low-noise probes first (missing header), then tampered tokens
138
+ 3. Every 200 on a guarded route without valid auth is a critical finding
139
+
140
+ Your voice: Quiet, methodical, thinks in attack graphs, never skips auth.
141
+ """
142
+
143
+ def get_tool_permissions(self) -> list[str]:
144
+ return ["run_tests", "browser_test", "screenshot", "get_scan_results", "get_brain"]
145
+
146
+ def craft_inputs(self, routes: list[str] | None = None) -> list[dict]:
147
+ """Auth-bypass probe cases for the given guarded routes."""
148
+ probes: list[dict] = []
149
+ for route in routes or ["/admin", "/api/users/1"]:
150
+ probes.append({"route": route, "headers": {}, "note": "no auth header"})
151
+ probes.append(
152
+ {
153
+ "route": route,
154
+ "headers": {"Authorization": "Bearer eyJhbGciOiJub25lIn0.e30."},
155
+ "note": "alg=none JWT",
156
+ }
157
+ )
158
+ probes.append(
159
+ {
160
+ "route": route,
161
+ "headers": {"Authorization": "Bearer invalid.token.here"},
162
+ "note": "garbage token",
163
+ }
164
+ )
165
+ return probes
@@ -0,0 +1,349 @@
1
+ """
2
+ Base Persona Class — Foundation for all Council personas.
3
+
4
+ Each persona is a specialized AI agent with:
5
+ - Unique system prompt defining expertise and perspective
6
+ - Tool access permissions (which tools it can call)
7
+ - Decision-making style (cautious, aggressive, balanced, etc.)
8
+ - Memory of past decisions for learning
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import logging
15
+ from abc import ABC, abstractmethod
16
+ from collections.abc import Callable
17
+ from dataclasses import dataclass, field
18
+ from datetime import UTC, datetime
19
+ from enum import Enum
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from patchi.core.ai.client import call_ai_structured
24
+ from patchi.core.ai.prompts import Skill, get_system_prompt
25
+ from patchi.core.brain.layered_brain import Layer
26
+
27
+ _log = logging.getLogger("patchi.brain.personas")
28
+
29
+
30
+ class PersonaStyle(Enum):
31
+ """Decision-making style of the persona."""
32
+
33
+ CAUTIOUS = "cautious" # Prefers safe, proven approaches
34
+ AGGRESSIVE = "aggressive" # Pushes boundaries, finds edge cases
35
+ BALANCED = "balanced" # Weighs tradeoffs carefully
36
+ INNOVATIVE = "innovative" # Seeks novel solutions
37
+ PRAGMATIC = "pragmatic" # Focuses on practical outcomes
38
+
39
+
40
+ @dataclass
41
+ class PersonaDecision:
42
+ """A single decision from a persona."""
43
+
44
+ persona_name: str
45
+ issue: str
46
+ analysis: str
47
+ recommendation: str
48
+ confidence: float # 0.0 - 1.0
49
+ tools_needed: list[str] = field(default_factory=list)
50
+ risks: list[str] = field(default_factory=list)
51
+ timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
52
+ reasoning_trace: list[str] = field(default_factory=list)
53
+
54
+
55
+ @dataclass
56
+ class PersonaMemory:
57
+ """Long-term memory for a persona."""
58
+
59
+ decisions: list[PersonaDecision] = field(default_factory=list)
60
+ patterns_learned: dict[str, Any] = field(default_factory=dict)
61
+ success_rates: dict[str, float] = field(default_factory=dict)
62
+ last_updated: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
63
+
64
+
65
+ class BasePersona(ABC):
66
+ """
67
+ Abstract base class for all Council personas.
68
+
69
+ Each persona must implement:
70
+ - analyze(): Core analysis logic
71
+ - get_tool_permissions(): Which tools this persona can call
72
+ - get_style(): Decision-making style
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ name: str,
78
+ root: Path,
79
+ brain_layers: dict[str, Layer],
80
+ project_context: dict,
81
+ config: dict,
82
+ on_progress: Callable[[str], None] | None = None,
83
+ ):
84
+ self.name = name
85
+ self.root = root
86
+ self.layers = brain_layers
87
+ self.project_context = project_context
88
+ self.config = config
89
+ self.on_progress = on_progress or (lambda _: None)
90
+ self.memory = self._load_memory()
91
+ self._decision_count = 0
92
+
93
+ @abstractmethod
94
+ def get_expertise_areas(self) -> list[str]:
95
+ """Return list of domains this persona specializes in."""
96
+ pass
97
+
98
+ @abstractmethod
99
+ def get_style(self) -> PersonaStyle:
100
+ """Return the persona's decision-making style."""
101
+ pass
102
+
103
+ @abstractmethod
104
+ def get_system_prompt_additions(self) -> str:
105
+ """Additional system prompt content specific to this persona."""
106
+ pass
107
+
108
+ def get_tool_permissions(self) -> list[str]:
109
+ """Return list of tool names this persona is allowed to call."""
110
+ # Default: all tools. Override to restrict.
111
+ return ["*"]
112
+
113
+ def _load_memory(self) -> PersonaMemory:
114
+ """Load persona memory from disk."""
115
+ mem_path = self.root / ".patchi" / "memory" / f"persona_{self.name.lower()}.json"
116
+ if mem_path.exists():
117
+ try:
118
+ data = json.loads(mem_path.read_text(encoding="utf-8"))
119
+ return PersonaMemory(
120
+ decisions=[PersonaDecision(**d) for d in data.get("decisions", [])],
121
+ patterns_learned=data.get("patterns_learned", {}),
122
+ success_rates=data.get("success_rates", {}),
123
+ last_updated=data.get("last_updated", ""),
124
+ )
125
+ except Exception as e:
126
+ _log.warning(f"Failed to load memory for {self.name}: {e}")
127
+ return PersonaMemory()
128
+
129
+ def _save_memory(self) -> None:
130
+ """Save persona memory to disk."""
131
+ mem_path = self.root / ".patchi" / "memory" / f"persona_{self.name.lower()}.json"
132
+ mem_path.parent.mkdir(parents=True, exist_ok=True)
133
+ data = {
134
+ "decisions": [d.__dict__ for d in self.memory.decisions[-100:]], # Keep last 100
135
+ "patterns_learned": self.memory.patterns_learned,
136
+ "success_rates": self.memory.success_rates,
137
+ "last_updated": datetime.now(UTC).isoformat(),
138
+ }
139
+ try:
140
+ mem_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
141
+ except Exception as e:
142
+ _log.warning(f"Failed to save memory for {self.name}: {e}")
143
+
144
+ def _build_context(self, issue: str, additional_context: dict = None) -> str:
145
+ """Build context string for AI analysis."""
146
+ ctx_parts = [
147
+ "=== PROJECT CONTEXT ===",
148
+ f"Project: {self.project_context.get('project_purpose', 'Unknown')}",
149
+ f"Domain: {self.project_context.get('project_domain', 'Unknown')}",
150
+ f"Frameworks: {', '.join(f.get('name', '') for f in self.project_context.get('frameworks', []))}",
151
+ f"Active Security Domains: {', '.join(self.project_context.get('active_security_domains', []))}",
152
+ "",
153
+ "=== LAYERED BRAIN SUMMARY ===",
154
+ ]
155
+
156
+ # Add relevant layer summaries
157
+ for _layer_name, layer in self.layers.items():
158
+ if layer.level <= 2: # Project and subsystem levels
159
+ ctx_parts.append(f"[{layer.name}] {layer.summary}")
160
+
161
+ if additional_context:
162
+ ctx_parts.append("\n=== ADDITIONAL CONTEXT ===")
163
+ ctx_parts.append(json.dumps(additional_context, indent=2))
164
+
165
+ ctx_parts.append("\n=== CURRENT ISSUE ===")
166
+ ctx_parts.append(issue)
167
+
168
+ return "\n".join(ctx_parts)
169
+
170
+ def _call_ai_with_persona(
171
+ self,
172
+ prompt: str,
173
+ system_prompt: str = None,
174
+ max_tokens: int = 2000,
175
+ temperature: float = 0.3,
176
+ ) -> dict | None:
177
+ """Call AI with persona-specific system prompt."""
178
+ if system_prompt is None:
179
+ base_prompt = get_system_prompt(Skill.DEEP_ANALYSIS)
180
+ system_prompt = f"{base_prompt}\n\n{self.get_system_prompt_additions()}"
181
+
182
+ try:
183
+ result = call_ai_structured(
184
+ config=self.config,
185
+ system_prompt=system_prompt,
186
+ user_prompt=prompt,
187
+ max_tokens=max_tokens,
188
+ temperature=temperature,
189
+ )
190
+ return result
191
+ except Exception as e:
192
+ _log.warning(f"{self.name} AI call failed: {e}")
193
+ return None
194
+
195
+ def analyze(self, issue: str, context: dict = None) -> PersonaDecision:
196
+ """
197
+ Analyze an issue and produce a decision.
198
+
199
+ This is the main entry point called by the Council.
200
+ """
201
+ self._decision_count += 1
202
+ self.on_progress(f"[{self.name}] Analyzing: {issue[:80]}...")
203
+
204
+ # Build context
205
+ self._build_context(issue, context)
206
+
207
+ # Create analysis prompt
208
+ analysis_prompt = f"""
209
+ Analyze the following issue from your perspective as {self.name}.
210
+
211
+ Your expertise: {", ".join(self.get_expertise_areas())}
212
+ Your style: {self.get_style().value}
213
+
214
+ Issue: {issue}
215
+
216
+ Provide your analysis in this JSON format:
217
+ {{
218
+ "analysis": "Your detailed analysis of the issue",
219
+ "recommendation": "Your specific recommendation for action",
220
+ "confidence": 0.85,
221
+ "tools_needed": ["tool1", "tool2"],
222
+ "risks": ["risk1", "risk2"],
223
+ "reasoning_trace": ["step 1", "step 2", "step 3"]
224
+ }}
225
+ """
226
+
227
+ result = self._call_ai_with_persona(analysis_prompt, max_tokens=3000)
228
+
229
+ if result:
230
+ decision = PersonaDecision(
231
+ persona_name=self.name,
232
+ issue=issue,
233
+ analysis=result.get("analysis", ""),
234
+ recommendation=result.get("recommendation", ""),
235
+ confidence=float(result.get("confidence", 0.5)),
236
+ tools_needed=result.get("tools_needed", []),
237
+ risks=result.get("risks", []),
238
+ reasoning_trace=result.get("reasoning_trace", []),
239
+ )
240
+ else:
241
+ # Fallback heuristic decision
242
+ decision = self._heuristic_decision(issue, context)
243
+
244
+ # Store in memory
245
+ self.memory.decisions.append(decision)
246
+ self._save_memory()
247
+
248
+ self.on_progress(
249
+ f"[{self.name}] Decision: {decision.recommendation[:80]}... (confidence: {decision.confidence:.0%})"
250
+ )
251
+ return decision
252
+
253
+ def _heuristic_decision(self, issue: str, context: dict = None) -> PersonaDecision:
254
+ """Fallback heuristic decision when AI is unavailable."""
255
+ return PersonaDecision(
256
+ persona_name=self.name,
257
+ issue=issue,
258
+ analysis=f"Heuristic analysis for: {issue}",
259
+ recommendation="Run standard scan and review findings manually",
260
+ confidence=0.4,
261
+ tools_needed=["scan_project"],
262
+ risks=["AI unavailable - limited analysis"],
263
+ reasoning_trace=["AI call failed, using fallback"],
264
+ )
265
+
266
+ def record_outcome(self, decision: PersonaDecision, success: bool, details: str = "") -> None:
267
+ """Record the outcome of a decision for learning."""
268
+ key = decision.issue[:50]
269
+ current = self.memory.success_rates.get(key, 0.5)
270
+ # Exponential moving average
271
+ self.memory.success_rates[key] = current * 0.7 + (1.0 if success else 0.0) * 0.3
272
+ self.memory.patterns_learned[key] = {
273
+ "last_outcome": "success" if success else "failure",
274
+ "details": details,
275
+ "timestamp": datetime.now(UTC).isoformat(),
276
+ }
277
+ self._save_memory()
278
+
279
+ def get_relevant_context(self, issue: str) -> dict:
280
+ """Extract relevant context from brain layers for this issue."""
281
+ # Use reasoning engine to find relevant layers
282
+ from patchi.core.brain.reasoning import ReasoningEngine
283
+
284
+ ReasoningEngine(self.root)
285
+ relevant = {}
286
+
287
+ # Find layers matching issue keywords
288
+ issue_lower = issue.lower()
289
+ for name, layer in self.layers.items():
290
+ layer_text = f"{name} {layer.summary} {layer.purpose}".lower()
291
+ # Simple keyword matching
292
+ if any(word in layer_text for word in issue_lower.split() if len(word) > 3):
293
+ relevant[name] = {
294
+ "level": layer.level,
295
+ "summary": layer.summary,
296
+ "purpose": layer.purpose,
297
+ "depends_on": layer.depends_on,
298
+ "dependents": layer.dependents,
299
+ }
300
+
301
+ return relevant
302
+
303
+
304
+ # ── Persona Registry ────────────────────────────────────────────────────────────
305
+
306
+ PERSONA_REGISTRY: dict[str, type[BasePersona]] = {}
307
+
308
+
309
+ def register_persona(cls: type[BasePersona]) -> type[BasePersona]:
310
+ """Decorator to register a persona class."""
311
+ PERSONA_REGISTRY[cls.__name__] = cls
312
+ return cls
313
+
314
+
315
+ def get_persona_class(name: str) -> type[BasePersona] | None:
316
+ """Get a persona class by name."""
317
+ return PERSONA_REGISTRY.get(name)
318
+
319
+
320
+ def list_personas() -> list[str]:
321
+ """List all registered persona names."""
322
+ return list(PERSONA_REGISTRY.keys())
323
+
324
+
325
+ def create_persona(
326
+ name: str,
327
+ root: Path,
328
+ brain_layers: dict[str, Layer],
329
+ project_context: dict,
330
+ config: dict,
331
+ on_progress: Callable[[str], None] = None,
332
+ ) -> BasePersona | None:
333
+ """Factory function to create a persona instance."""
334
+ cls = get_persona_class(name)
335
+ if cls is None:
336
+ _log.warning(f"Unknown persona: {name}")
337
+ return None
338
+ try:
339
+ return cls(
340
+ name=name,
341
+ root=root,
342
+ brain_layers=brain_layers,
343
+ project_context=project_context,
344
+ config=config,
345
+ on_progress=on_progress,
346
+ )
347
+ except Exception as e:
348
+ _log.error(f"Failed to create persona {name}: {e}")
349
+ return None