codejury 0.10.0__tar.gz → 0.10.2__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 (190) hide show
  1. {codejury-0.10.0 → codejury-0.10.2}/PKG-INFO +13 -13
  2. {codejury-0.10.0 → codejury-0.10.2}/README.md +12 -12
  3. {codejury-0.10.0 → codejury-0.10.2}/codejury/__init__.py +2 -2
  4. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/__init__.py +1 -1
  5. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/debate.py +9 -6
  6. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/mock.py +2 -3
  7. codejury-0.10.2/codejury/agents/parsing.py +45 -0
  8. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/refuter.py +3 -3
  9. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/verifier.py +11 -7
  10. {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/__init__.py +1 -1
  11. {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/provenance.py +5 -5
  12. {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/taint.py +6 -6
  13. {codejury-0.10.0 → codejury-0.10.2}/codejury/assembly.py +2 -2
  14. {codejury-0.10.0 → codejury-0.10.2}/codejury/baseline.py +13 -17
  15. {codejury-0.10.0 → codejury-0.10.2}/codejury/cli.py +22 -14
  16. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/business_logic.yaml +1 -1
  17. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/dependency_config.yaml +3 -3
  18. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/excessive_agency.yaml +3 -3
  19. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/input_validation.yaml +6 -6
  20. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/insecure_output_handling.yaml +2 -2
  21. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/prompt_injection.yaml +2 -2
  22. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/secrets.yaml +5 -5
  23. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_fixed_enum_safe.yaml +1 -1
  24. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_model_confirmed_vuln.yaml +1 -1
  25. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_sha256_checksum_safe.yaml +1 -1
  26. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/deserialize_json_safe.yaml +1 -1
  27. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/deserialize_pickle_vuln.yaml +1 -1
  28. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_json_response_safe.yaml +2 -2
  29. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +1 -1
  30. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_user_content_concat_safe.yaml +1 -1
  31. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/session_secure_cookie_safe.yaml +1 -1
  32. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sql_constant_concat_safe.yaml +1 -1
  33. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_constant_url_safe.yaml +1 -1
  34. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +2 -2
  35. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +1 -1
  36. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_path_sanitized_safe.yaml +2 -2
  37. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_path_tainted_vuln.yaml +2 -2
  38. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_textcontent_safe.yaml +1 -1
  39. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/suppressions.yaml +1 -1
  40. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/taint.yaml +4 -4
  41. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/__init__.py +1 -1
  42. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/artifact.py +1 -1
  43. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/capability.py +17 -4
  44. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/context.py +1 -1
  45. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/observation.py +27 -6
  46. {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/result.py +1 -1
  47. {codejury-0.10.0 → codejury-0.10.2}/codejury/evaluation.py +14 -8
  48. codejury-0.10.2/codejury/infrastructure/__init__.py +4 -0
  49. {codejury-0.10.0 → codejury-0.10.2}/codejury/infrastructure/cache.py +4 -4
  50. {codejury-0.10.0 → codejury-0.10.2}/codejury/infrastructure/json_parse.py +8 -3
  51. codejury-0.10.2/codejury/integrations/__init__.py +1 -0
  52. {codejury-0.10.0 → codejury-0.10.2}/codejury/integrations/github.py +24 -9
  53. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/__init__.py +1 -1
  54. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/adaptive.py +2 -2
  55. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/base.py +10 -2
  56. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/challenge.py +12 -2
  57. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/debate.py +1 -1
  58. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/pipeline.py +1 -1
  59. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/reflexion.py +1 -1
  60. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/single.py +1 -1
  61. {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/taint_gate.py +3 -3
  62. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/__init__.py +1 -1
  63. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/anthropic.py +1 -1
  64. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/litellm.py +1 -1
  65. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/mock.py +1 -1
  66. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/openai.py +1 -1
  67. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/retry.py +12 -5
  68. {codejury-0.10.0 → codejury-0.10.2}/codejury/reporting.py +17 -12
  69. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/__init__.py +1 -1
  70. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/callers.py +8 -6
  71. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/chunker.py +3 -3
  72. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/diff.py +6 -5
  73. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/function.py +13 -4
  74. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/mock.py +1 -1
  75. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/repo.py +5 -1
  76. {codejury-0.10.0 → codejury-0.10.2}/codejury/suppression.py +14 -16
  77. {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/__init__.py +1 -1
  78. {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/base.py +4 -2
  79. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/PKG-INFO +13 -13
  80. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/SOURCES.txt +2 -0
  81. {codejury-0.10.0 → codejury-0.10.2}/pyproject.toml +1 -1
  82. {codejury-0.10.0 → codejury-0.10.2}/tests/test_cache.py +1 -1
  83. {codejury-0.10.0 → codejury-0.10.2}/tests/test_evaluation.py +1 -1
  84. codejury-0.10.2/tests/test_parsing.py +32 -0
  85. codejury-0.10.2/tests/test_review_fixes.py +100 -0
  86. {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint.py +2 -2
  87. {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint_gate.py +2 -2
  88. codejury-0.10.0/codejury/agents/parsing.py +0 -42
  89. codejury-0.10.0/codejury/infrastructure/__init__.py +0 -4
  90. codejury-0.10.0/codejury/integrations/__init__.py +0 -1
  91. {codejury-0.10.0 → codejury-0.10.2}/LICENSE +0 -0
  92. {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/base.py +0 -0
  93. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/authentication.yaml +0 -0
  94. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/authorization.yaml +0 -0
  95. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/crypto.yaml +0 -0
  96. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/data_protection.yaml +0 -0
  97. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/error_logging.yaml +0 -0
  98. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/output_encoding.yaml +0 -0
  99. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/session.yaml +0 -0
  100. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_allowlist_safe.yaml +0 -0
  101. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +0 -0
  102. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +0 -0
  103. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_human_approval_safe.yaml +0 -0
  104. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_bcrypt_password.yaml +0 -0
  105. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +0 -0
  106. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_jwt_verified_safe.yaml +0 -0
  107. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_sha256_password.yaml +0 -0
  108. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +0 -0
  109. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authz_idor_vuln.yaml +0 -0
  110. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authz_owner_safe.yaml +0 -0
  111. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +0 -0
  112. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/business_logic_server_checked_safe.yaml +0 -0
  113. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +0 -0
  114. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_ossystem_vuln.yaml +0 -0
  115. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_subprocess_safe.yaml +0 -0
  116. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/crypto_aesgcm_safe.yaml +0 -0
  117. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/crypto_ecb_vuln.yaml +0 -0
  118. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +0 -0
  119. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/data_protection_tokenized_safe.yaml +0 -0
  120. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +0 -0
  121. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +0 -0
  122. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/error_logging_redacted_safe.yaml +0 -0
  123. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +0 -0
  124. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_escaped_output_safe.yaml +0 -0
  125. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_exec_output_vuln.yaml +0 -0
  126. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +0 -0
  127. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_schema_validated_safe.yaml +0 -0
  128. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/literal_eval_safe.yaml +0 -0
  129. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_basename_safe.yaml +0 -0
  130. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_contained_safe.yaml +0 -0
  131. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_traversal_vuln.yaml +0 -0
  132. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_delimited_data_safe.yaml +0 -0
  133. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_format_role_vuln.yaml +0 -0
  134. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_indirect_rag_vuln.yaml +0 -0
  135. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_system_concat_vuln.yaml +0 -0
  136. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_user_role_safe.yaml +0 -0
  137. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/secrets_env_safe.yaml +0 -0
  138. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/secrets_hardcoded_vuln.yaml +0 -0
  139. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/session_fixation_vuln.yaml +0 -0
  140. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_format_vuln.yaml +0 -0
  141. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_fstring_query.yaml +0 -0
  142. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_indirect_var_vuln.yaml +0 -0
  143. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_parameterized_query.yaml +0 -0
  144. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_allowlist_safe.yaml +0 -0
  145. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +0 -0
  146. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_user_url_vuln.yaml +0 -0
  147. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +0 -0
  148. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_innerhtml_vuln.yaml +0 -0
  149. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/tasks/audit_diff_debate.yaml +0 -0
  150. {codejury-0.10.0 → codejury-0.10.2}/codejury/data/tasks/quick_scan_single.yaml +0 -0
  151. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/base.py +0 -0
  152. {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/openai_format.py +0 -0
  153. {codejury-0.10.0 → codejury-0.10.2}/codejury/resources.py +0 -0
  154. {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/base.py +0 -0
  155. {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/registry.py +0 -0
  156. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/dependency_links.txt +0 -0
  157. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/entry_points.txt +0 -0
  158. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/requires.txt +0 -0
  159. {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/top_level.txt +0 -0
  160. {codejury-0.10.0 → codejury-0.10.2}/setup.cfg +0 -0
  161. {codejury-0.10.0 → codejury-0.10.2}/tests/test_adaptive.py +0 -0
  162. {codejury-0.10.0 → codejury-0.10.2}/tests/test_anthropic_provider.py +0 -0
  163. {codejury-0.10.0 → codejury-0.10.2}/tests/test_assembly.py +0 -0
  164. {codejury-0.10.0 → codejury-0.10.2}/tests/test_audit_pipeline.py +0 -0
  165. {codejury-0.10.0 → codejury-0.10.2}/tests/test_baseline.py +0 -0
  166. {codejury-0.10.0 → codejury-0.10.2}/tests/test_callers.py +0 -0
  167. {codejury-0.10.0 → codejury-0.10.2}/tests/test_capability.py +0 -0
  168. {codejury-0.10.0 → codejury-0.10.2}/tests/test_challenge.py +0 -0
  169. {codejury-0.10.0 → codejury-0.10.2}/tests/test_cli_audit.py +0 -0
  170. {codejury-0.10.0 → codejury-0.10.2}/tests/test_context.py +0 -0
  171. {codejury-0.10.0 → codejury-0.10.2}/tests/test_debate_agents.py +0 -0
  172. {codejury-0.10.0 → codejury-0.10.2}/tests/test_debate_orchestrator.py +0 -0
  173. {codejury-0.10.0 → codejury-0.10.2}/tests/test_diff_source.py +0 -0
  174. {codejury-0.10.0 → codejury-0.10.2}/tests/test_function_source.py +0 -0
  175. {codejury-0.10.0 → codejury-0.10.2}/tests/test_integrations.py +0 -0
  176. {codejury-0.10.0 → codejury-0.10.2}/tests/test_json_parse.py +0 -0
  177. {codejury-0.10.0 → codejury-0.10.2}/tests/test_litellm_provider.py +0 -0
  178. {codejury-0.10.0 → codejury-0.10.2}/tests/test_openai_provider.py +0 -0
  179. {codejury-0.10.0 → codejury-0.10.2}/tests/test_orchestrator.py +0 -0
  180. {codejury-0.10.0 → codejury-0.10.2}/tests/test_pipeline_orchestrator.py +0 -0
  181. {codejury-0.10.0 → codejury-0.10.2}/tests/test_provenance.py +0 -0
  182. {codejury-0.10.0 → codejury-0.10.2}/tests/test_reflexion_orchestrator.py +0 -0
  183. {codejury-0.10.0 → codejury-0.10.2}/tests/test_repo_source.py +0 -0
  184. {codejury-0.10.0 → codejury-0.10.2}/tests/test_reporting.py +0 -0
  185. {codejury-0.10.0 → codejury-0.10.2}/tests/test_retry_provider.py +0 -0
  186. {codejury-0.10.0 → codejury-0.10.2}/tests/test_sarif.py +0 -0
  187. {codejury-0.10.0 → codejury-0.10.2}/tests/test_suppression.py +0 -0
  188. {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint_crossfile.py +0 -0
  189. {codejury-0.10.0 → codejury-0.10.2}/tests/test_tasks.py +0 -0
  190. {codejury-0.10.0 → codejury-0.10.2}/tests/test_verifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: General-purpose Application Security AI audit framework -- five-layer architecture, capabilities as first-class data
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -32,22 +32,22 @@ Dynamic: license-file
32
32
 
33
33
  An AI security auditor for code whose knowledge lives in versioned YAML, not in
34
34
  prompts. It reviews a diff or a whole repository against the OWASP ASVS and
35
- reports a verdict per dimension -- both what is **vulnerable** and what is
35
+ reports a verdict per dimension: both what is **vulnerable** and what is
36
36
  **verified safe**.
37
37
 
38
- The name is the core idea: code goes before a "jury" of adversarial roles --
39
- Finder / Challenger / Judge -- that argue and converge on a verdict.
38
+ The name is the core idea: code goes before a "jury" of adversarial roles,
39
+ Finder / Challenger / Judge, that argue and converge on a verdict.
40
40
 
41
41
  Why it is built this way:
42
42
 
43
43
  - **Knowledge is data.** Each OWASP ASVS area (and now OWASP LLM Top 10 areas) is a YAML capability
44
- (safe patterns + anti-patterns, with CWE and examples) -- versioned, reviewable
44
+ (safe patterns + anti-patterns, with CWE and examples), versioned, reviewable
45
45
  in a PR, and editable by non-engineers. The framework core stays small.
46
46
  - **Verdicts, not just alerts.** Every capability yields `SECURE` / `VULNERABLE`
47
47
  / `PARTIAL` / `NOT_PRESENT`, so a report shows what was checked and *passed*,
48
48
  not only what failed.
49
49
  - **Composable.** Four orchestration strategies, four model backends, and
50
- diff / repo inputs are chosen per run -- mix and match.
50
+ diff / repo inputs are chosen per run; mix and match.
51
51
 
52
52
  ## Install
53
53
 
@@ -65,7 +65,7 @@ git diff origin/main... | codejury audit --fail-on high -
65
65
  # Post inline review comments on a GitHub pull request (needs GITHUB_TOKEN)
66
66
  git diff origin/main... | codejury audit --github your-org/your-repo#123 -
67
67
 
68
- # No API key needed -- prove the pipeline runs end to end with mock layers
68
+ # No API key needed: prove the pipeline runs end to end with mock layers
69
69
  codejury dry-run
70
70
 
71
71
  # A real audit: set a key, then review your staged changes
@@ -104,7 +104,7 @@ caller/callee context). It downgrades only on positive proof, so it removes fals
104
104
  positives without dropping real findings.
105
105
 
106
106
  `--orchestrator adaptive` keeps cost down: it runs the single verifier first and
107
- escalates to a full debate only for artifacts worth it -- any VULNERABLE verdict
107
+ escalates to a full debate only for artifacts worth it: any VULNERABLE verdict
108
108
  (verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
109
109
  confident files pay a single call; only the rest pay for debate.
110
110
 
@@ -118,7 +118,7 @@ outside C/C++) are dropped by versioned rules in
118
118
 
119
119
  `codejury eval` takes `--dataset <dir>` (golden YAML directory), `--split <name>`
120
120
  (score only cases tagged with that `split:`, e.g. a held-out set), and
121
- `--format {text,json}` -- the JSON report is a stable schema (overall plus
121
+ `--format {text,json}`; the JSON report is a stable schema (overall plus
122
122
  per-capability confusion matrix and precision / recall / F1).
123
123
 
124
124
  Runs are deterministic: providers query at temperature 0, and `audit` / `scan`
@@ -139,7 +139,7 @@ codejury scan ./myrepo --only secrets,input_validation,crypto
139
139
  ## Configuration
140
140
 
141
141
  Provider keys are read from the environment (codejury does **not** auto-load
142
- `.env` -- copy `.env.example` and `source` it):
142
+ `.env`; copy `.env.example` and `source` it):
143
143
 
144
144
  | Variable | Used by |
145
145
  |---|---|
@@ -180,7 +180,7 @@ what `--only` and a task's `capabilities:` accept:
180
180
  `business_logic` · `dependency_config` · `prompt_injection` ·
181
181
  `insecure_output_handling` · `excessive_agency`
182
182
 
183
- To tune for your codebase, edit these files (add patterns / sharpen wording) --
183
+ To tune for your codebase, edit these files (add patterns / sharpen wording);
184
184
  no code change needed.
185
185
 
186
186
  ## Architecture
@@ -210,13 +210,13 @@ independently.
210
210
  can't see whether a value is attacker-controlled. Mitigations that add context
211
211
  but do not fully solve it: `scan --callers` (where this file's functions are
212
212
  called) and `scan --callees` (the called code it delegates to, so a sink in
213
- another file is visible) -- pair them for both directions; `--orchestrator
213
+ another file is visible); pair them for both directions; `--orchestrator
214
214
  challenge` (a recall-safe
215
215
  refutation pass that drops only provably-safe flags); `--only` to scope; or
216
216
  `--orchestrator debate`. Real taint precision still needs data-flow analysis,
217
217
  not model skepticism.
218
218
  - **`scan` cost scales as files x capabilities.** It is a periodic deep audit,
219
- not a quick check -- scope it with `--only`. Day to day, audit the diff.
219
+ not a quick check; scope it with `--only`. Day to day, audit the diff.
220
220
 
221
221
  ## Development
222
222
 
@@ -2,22 +2,22 @@
2
2
 
3
3
  An AI security auditor for code whose knowledge lives in versioned YAML, not in
4
4
  prompts. It reviews a diff or a whole repository against the OWASP ASVS and
5
- reports a verdict per dimension -- both what is **vulnerable** and what is
5
+ reports a verdict per dimension: both what is **vulnerable** and what is
6
6
  **verified safe**.
7
7
 
8
- The name is the core idea: code goes before a "jury" of adversarial roles --
9
- Finder / Challenger / Judge -- that argue and converge on a verdict.
8
+ The name is the core idea: code goes before a "jury" of adversarial roles,
9
+ Finder / Challenger / Judge, that argue and converge on a verdict.
10
10
 
11
11
  Why it is built this way:
12
12
 
13
13
  - **Knowledge is data.** Each OWASP ASVS area (and now OWASP LLM Top 10 areas) is a YAML capability
14
- (safe patterns + anti-patterns, with CWE and examples) -- versioned, reviewable
14
+ (safe patterns + anti-patterns, with CWE and examples), versioned, reviewable
15
15
  in a PR, and editable by non-engineers. The framework core stays small.
16
16
  - **Verdicts, not just alerts.** Every capability yields `SECURE` / `VULNERABLE`
17
17
  / `PARTIAL` / `NOT_PRESENT`, so a report shows what was checked and *passed*,
18
18
  not only what failed.
19
19
  - **Composable.** Four orchestration strategies, four model backends, and
20
- diff / repo inputs are chosen per run -- mix and match.
20
+ diff / repo inputs are chosen per run; mix and match.
21
21
 
22
22
  ## Install
23
23
 
@@ -35,7 +35,7 @@ git diff origin/main... | codejury audit --fail-on high -
35
35
  # Post inline review comments on a GitHub pull request (needs GITHUB_TOKEN)
36
36
  git diff origin/main... | codejury audit --github your-org/your-repo#123 -
37
37
 
38
- # No API key needed -- prove the pipeline runs end to end with mock layers
38
+ # No API key needed: prove the pipeline runs end to end with mock layers
39
39
  codejury dry-run
40
40
 
41
41
  # A real audit: set a key, then review your staged changes
@@ -74,7 +74,7 @@ caller/callee context). It downgrades only on positive proof, so it removes fals
74
74
  positives without dropping real findings.
75
75
 
76
76
  `--orchestrator adaptive` keeps cost down: it runs the single verifier first and
77
- escalates to a full debate only for artifacts worth it -- any VULNERABLE verdict
77
+ escalates to a full debate only for artifacts worth it: any VULNERABLE verdict
78
78
  (verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
79
79
  confident files pay a single call; only the rest pay for debate.
80
80
 
@@ -88,7 +88,7 @@ outside C/C++) are dropped by versioned rules in
88
88
 
89
89
  `codejury eval` takes `--dataset <dir>` (golden YAML directory), `--split <name>`
90
90
  (score only cases tagged with that `split:`, e.g. a held-out set), and
91
- `--format {text,json}` -- the JSON report is a stable schema (overall plus
91
+ `--format {text,json}`; the JSON report is a stable schema (overall plus
92
92
  per-capability confusion matrix and precision / recall / F1).
93
93
 
94
94
  Runs are deterministic: providers query at temperature 0, and `audit` / `scan`
@@ -109,7 +109,7 @@ codejury scan ./myrepo --only secrets,input_validation,crypto
109
109
  ## Configuration
110
110
 
111
111
  Provider keys are read from the environment (codejury does **not** auto-load
112
- `.env` -- copy `.env.example` and `source` it):
112
+ `.env`; copy `.env.example` and `source` it):
113
113
 
114
114
  | Variable | Used by |
115
115
  |---|---|
@@ -150,7 +150,7 @@ what `--only` and a task's `capabilities:` accept:
150
150
  `business_logic` · `dependency_config` · `prompt_injection` ·
151
151
  `insecure_output_handling` · `excessive_agency`
152
152
 
153
- To tune for your codebase, edit these files (add patterns / sharpen wording) --
153
+ To tune for your codebase, edit these files (add patterns / sharpen wording);
154
154
  no code change needed.
155
155
 
156
156
  ## Architecture
@@ -180,13 +180,13 @@ independently.
180
180
  can't see whether a value is attacker-controlled. Mitigations that add context
181
181
  but do not fully solve it: `scan --callers` (where this file's functions are
182
182
  called) and `scan --callees` (the called code it delegates to, so a sink in
183
- another file is visible) -- pair them for both directions; `--orchestrator
183
+ another file is visible); pair them for both directions; `--orchestrator
184
184
  challenge` (a recall-safe
185
185
  refutation pass that drops only provably-safe flags); `--only` to scope; or
186
186
  `--orchestrator debate`. Real taint precision still needs data-flow analysis,
187
187
  not model skepticism.
188
188
  - **`scan` cost scales as files x capabilities.** It is a periodic deep audit,
189
- not a quick check -- scope it with `--only`. Day to day, audit the diff.
189
+ not a quick check; scope it with `--only`. Day to day, audit the diff.
190
190
 
191
191
  ## Development
192
192
 
@@ -1,6 +1,6 @@
1
- """codejury -- a general-purpose Application Security AI audit framework.
1
+ """codejury: a general-purpose Application Security AI audit framework.
2
2
 
3
- Five layers: Task / (Capability + Orchestrator + Source + Agent) / Provider / Infrastructure.
3
+ Five layers: Task, then Capability plus Orchestrator plus Source plus Agent, then Provider, then Infrastructure.
4
4
  Domain knowledge lives in YAML capability files as a first-class citizen,
5
5
  aligned with OWASP ASVS.
6
6
  """
@@ -1,4 +1,4 @@
1
- """codejury.agents -- the audit roles (finder / challenger / judge / verifier).
1
+ """codejury.agents: the audit roles (finder / challenger / judge / verifier).
2
2
 
3
3
  An agent reads an AnalysisContext and emits observations. It talks to a model
4
4
  only through a Provider, never a vendor SDK, so the role logic stays independent
@@ -19,21 +19,24 @@ from codejury.agents.parsing import one_of, str_list, to_evidence, to_float
19
19
  from codejury.domain.artifact import CodeArtifact
20
20
  from codejury.domain.capability import Capability
21
21
  from codejury.domain.context import AnalysisContext
22
- from codejury.domain.observation import Concession, Finding, Observation
22
+ from codejury.domain.observation import Concession, Finding, Observation, Severity
23
23
  from codejury.infrastructure.json_parse import extract_json_object
24
24
  from codejury.providers.base import Message, Provider
25
25
 
26
- _SEVERITY = {"CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"}
26
+ from typing import get_args
27
+
28
+ _SEVERITY = set(get_args(Severity)) # single source of truth: the Severity Literal
27
29
 
28
30
  _FINDING_SHAPE = (
29
31
  '{"capability": "id.sub", "title": "...", "severity": "HIGH", "cwe": "CWE-89", '
30
- '"description": "...", "evidence": [{"file": "...", "line": 0, "code": "..."}], "confidence": 0.0}'
32
+ '"description": "...", "recommendation": "...", "matched_anti": ["id"], '
33
+ '"evidence": [{"file": "...", "line": 0, "code": "..."}], "confidence": 0.0}'
31
34
  )
32
35
 
33
36
  _DEEP_LENS = (
34
37
  "Look past surface patterns for the deepest flaw:\n"
35
- "- Trust anchors: what does this code trust to authenticate or authorize -- a key, token, header, "
36
- "signature, role, or caller -- and who controls that value? If the attacker supplies what is used to "
38
+ "- Trust anchors: what does this code trust to authenticate or authorize, such as a key, token, header, "
39
+ "signature, role, or caller, and who controls that value? If the attacker supplies what is used to "
37
40
  "verify them (e.g. their own public key, an unconfigured key that disables verification), passing the "
38
41
  "check proves nothing.\n"
39
42
  "- Order of operations: is an external, irreversible, or privileged action performed before the local "
@@ -98,7 +101,7 @@ class ChallengerAgent(_DebateAgent):
98
101
  def run(self, ctx: AnalysisContext) -> list[Observation]:
99
102
  parts = [
100
103
  "Challenge the findings below. For each one you believe is a false positive, write a rebuttal. "
101
- "Add new_findings for any real issue that was missed -- especially a deeper flaw the finder "
104
+ "Add new_findings for any real issue that was missed, especially a deeper flaw the finder "
102
105
  "stopped short of.",
103
106
  _DEEP_LENS,
104
107
  _code(ctx.artifact),
@@ -1,9 +1,8 @@
1
- """MockAgent -- a minimal Agent for the dry-run and tests.
1
+ """MockAgent: a minimal Agent for the dry-run and tests.
2
2
 
3
3
  It really calls the provider (so the dry-run exercises the agent -> provider
4
4
  path), then emits one Verdict per in-scope capability, parking the model's reply
5
- in ``reasoning``. It does no real parsing or judgement; the Phase 3 VerifierAgent
6
- will.
5
+ in ``reasoning``. It does no real parsing or judgement; ``VerifierAgent`` does.
7
6
  """
8
7
 
9
8
  from __future__ import annotations
@@ -0,0 +1,45 @@
1
+ """Shared coercion from loosely-typed model JSON into domain values.
2
+
3
+ Agents parse model output that may omit, mistype, or invent fields. These
4
+ helpers coerce defensively: they never raise on bad input, they fall back.
5
+ """
6
+
7
+ import math
8
+
9
+ from codejury.domain.observation import Evidence
10
+
11
+
12
+ def str_list(value: object) -> list[str]:
13
+ return [str(x) for x in value] if isinstance(value, list) else []
14
+
15
+
16
+ def to_float(value: object, default: float) -> float:
17
+ # reject bool (it is an int subclass) and non-finite values (json.loads accepts
18
+ # NaN/Infinity); a NaN confidence would silently corrupt every downstream sort.
19
+ if isinstance(value, bool):
20
+ return default
21
+ try:
22
+ result = float(value) # type: ignore[arg-type]
23
+ except (TypeError, ValueError):
24
+ return default
25
+ return result if math.isfinite(result) else default
26
+
27
+
28
+ def one_of(value: object, allowed: set[str], default: str) -> str:
29
+ # `value in allowed` would raise TypeError on an unhashable model value
30
+ # (e.g. status -> [] or {}); guard so bad input falls back, never raises.
31
+ return value if isinstance(value, str) and value in allowed else default
32
+
33
+
34
+ def to_evidence(items: object) -> list[Evidence]:
35
+ if not isinstance(items, list):
36
+ return []
37
+ out: list[Evidence] = []
38
+ for e in items:
39
+ if not isinstance(e, dict):
40
+ continue
41
+ line = e.get("line")
42
+ # a location needs a real 1-based line; reject 0, negatives, and bool (an int subclass)
43
+ valid_line = line if isinstance(line, int) and not isinstance(line, bool) and line >= 1 else None
44
+ out.append(Evidence(file=str(e.get("file", "")), line=valid_line, code=str(e.get("code", ""))))
45
+ return out
@@ -1,8 +1,8 @@
1
- """RefuterAgent -- a skeptic that tries to dismiss flagged verdicts as false positives.
1
+ """RefuterAgent: a skeptic that tries to dismiss flagged verdicts as false positives.
2
2
 
3
3
  Used by the challenge orchestrator: the verifier flags issues, then the refuter
4
4
  gets the code plus the VULNERABLE verdicts (via ``ctx.history``) and argues which
5
- are false positives -- e.g. a value that is not actually attacker-controlled or a
5
+ are false positives, e.g. a value that is not actually attacker-controlled or a
6
6
  sink that is not reachable. It returns a Concession per verdict it refutes.
7
7
 
8
8
  This is the cheap, focused alternative to a full debate: only flagged verdicts
@@ -53,7 +53,7 @@ class RefuterAgent(Agent):
53
53
  "is not attacker-controlled: a stored data field, or traced (here or in the call sites) to "
54
54
  "a trusted, config, or operator-supplied source. If its origin is not shown or could "
55
55
  "plausibly be external input, do NOT refute. For other issue types (hardcoded secrets, "
56
- "weak crypto, ...), a literal value is often the vulnerability itself -- do NOT refute "
56
+ "weak crypto, ...), a literal value is often the vulnerability itself; do NOT refute "
57
57
  "those just because a value is constant.\n\n"
58
58
  "Respond with a single JSON object exactly like:\n" + _JSON_SHAPE
59
59
  )
@@ -1,9 +1,9 @@
1
- """VerifierAgent -- check code against a capability's correct/anti patterns.
1
+ """VerifierAgent: check code against a capability's correct/anti patterns.
2
2
 
3
3
  It renders the capability into a prompt, calls the provider once per capability,
4
4
  and parses the JSON reply into Verdicts. It asks for one verdict per
5
5
  sub_capability including SECURE / NOT_PRESENT, so a report can say what was
6
- checked and what passed -- not only what failed.
6
+ checked and what passed, not only what failed.
7
7
 
8
8
  Parsing is defensive: a missing or malformed reply yields no verdicts rather
9
9
  than raising, and unknown status values fall back to UNKNOWN.
@@ -15,11 +15,13 @@ from codejury.agents.base import Agent
15
15
  from codejury.agents.parsing import one_of, str_list, to_evidence, to_float
16
16
  from codejury.domain.capability import Capability
17
17
  from codejury.domain.context import AnalysisContext
18
- from codejury.domain.observation import Observation, Verdict
18
+ from codejury.domain.observation import Observation, Verdict, VerdictStatus
19
19
  from codejury.infrastructure.json_parse import extract_json_object
20
20
  from codejury.providers.base import Message, Provider
21
21
 
22
- _VALID_STATUS = {"SECURE", "VULNERABLE", "PARTIAL", "NOT_PRESENT", "UNKNOWN"}
22
+ from typing import get_args
23
+
24
+ _VALID_STATUS = set(get_args(VerdictStatus)) # single source of truth: the VerdictStatus Literal
23
25
 
24
26
  _SYSTEM = (
25
27
  "You are a security verifier. You check code against a checklist of correct and "
@@ -73,7 +75,7 @@ def _render_capability(cap: Capability) -> str:
73
75
  def _build_prompt(path: str, content: str, cap: Capability, context: str = "") -> str:
74
76
  sub_names = ", ".join(cap.sub_capabilities) or "(none)"
75
77
  context_block = (
76
- f"Related code (call sites / usages elsewhere -- for tracing where values come from, "
78
+ f"Related code (call sites / usages elsewhere, for tracing where values come from, "
77
79
  f"NOT under review):\n```\n{context}\n```\n\n"
78
80
  if context
79
81
  else ""
@@ -88,7 +90,7 @@ def _build_prompt(path: str, content: str, cap: Capability, context: str = "") -
88
90
  "For input-driven issues (injection, path traversal, SSRF), mark VULNERABLE only when "
89
91
  "untrusted/external input could plausibly reach the sink in the code shown. A constant, "
90
92
  "a stored data field, a value from trusted config, or a path or argument the operator "
91
- "supplies (e.g. a CLI argument) is not attacker-controlled -- do not flag it.\n\n"
93
+ "supplies (e.g. a CLI argument) is not attacker-controlled; do not flag it.\n\n"
92
94
  "Respond with a single JSON object exactly like:\n" + _JSON_SHAPE
93
95
  )
94
96
 
@@ -108,8 +110,10 @@ def _anti_pattern_cwes(cap: Capability) -> dict[str, tuple[int, str]]:
108
110
 
109
111
 
110
112
  def _resolve_cwe(matched_anti: list[str], cwe_by_id: dict[str, tuple[int, str]]) -> str:
113
+ # the most severe matched anti-pattern's CWE; ties broken by CWE id so the result
114
+ # is fully determined by the inputs, not by dict/list ordering.
111
115
  matched = [cwe_by_id[a] for a in matched_anti if a in cwe_by_id]
112
- return max(matched, key=lambda rank_cwe: rank_cwe[0])[1] if matched else ""
116
+ return max(matched, key=lambda rank_cwe: (rank_cwe[0], rank_cwe[1]))[1] if matched else ""
113
117
 
114
118
 
115
119
  def _parse_verdicts(text: str, cap: Capability) -> list[Verdict]:
@@ -1,6 +1,6 @@
1
1
  """Static analysis for provenance (P1).
2
2
 
3
- The code-graph / data-flow engine that gives the verifier provenance -- whether a
3
+ The code-graph / data-flow engine that gives the verifier provenance: whether a
4
4
  value reaching a sink is attacker-controlled, sanitized, or a trusted constant.
5
5
  This is the real fix for the taint precision floor that single-file LLM review
6
6
  cannot reach (see ROADMAP P1). Python / AST based to start.
@@ -8,7 +8,7 @@ and literals a value derives from.
8
8
  The analysis is deliberately conservative and flow-insensitive: a name assigned
9
9
  more than once contributes the union of all its right-hand sides, so a possible
10
10
  source is never dropped (recall over precision). A value built only from literals
11
- is reported as ``is_constant`` -- the signal that distinguishes, for example,
11
+ is reported as ``is_constant``, the signal that distinguishes, for example,
12
12
  SQL concatenated from constants (safe) from SQL concatenated from a parameter.
13
13
 
14
14
  This module finds where a value comes from; it does not decide what is a source
@@ -43,7 +43,7 @@ class Origin:
43
43
 
44
44
  @property
45
45
  def is_constant(self) -> bool:
46
- """True when the value is built only from literals -- no param, call, attr,
46
+ """True when the value is built only from literals: no param, call, attr,
47
47
  free name, or unmodelled expression contributes."""
48
48
  return not (self.params or self.calls or self.attrs or self.globals_ or self.unknown)
49
49
 
@@ -65,7 +65,7 @@ def parse_function(source: str, name: str) -> ast.FunctionDef | ast.AsyncFunctio
65
65
 
66
66
 
67
67
  def find_calls(scope: ast.AST, callee: str) -> list[ast.Call]:
68
- """Every call within ``scope`` whose function is named ``callee`` -- matching a
68
+ """Every call within ``scope`` whose function is named ``callee``, matching a
69
69
  bare name (``open``) or the final attribute (``execute`` in ``cur.execute``)."""
70
70
  return [node for node in ast.walk(scope) if isinstance(node, ast.Call) and _call_name(node) == callee]
71
71
 
@@ -97,7 +97,7 @@ def trace_value(func: ast.FunctionDef | ast.AsyncFunctionDef, expr: ast.AST) ->
97
97
  on_param=lambda n: Origin(params=frozenset({n})),
98
98
  on_global=lambda n: Origin(globals_=frozenset({n})),
99
99
  on_constant=lambda: _LITERAL,
100
- on_cycle=Origin, # assignment cycle -- no new information
100
+ on_cycle=Origin, # assignment cycle, no new information
101
101
  )
102
102
 
103
103
 
@@ -182,7 +182,7 @@ def reduce_value(
182
182
  if isinstance(expr, (ast.List, ast.Tuple, ast.Set)):
183
183
  return combine([recurse(e) for e in expr.elts])
184
184
  if isinstance(expr, ast.Name):
185
- if expr.id in seen: # assignment cycle -- stop
185
+ if expr.id in seen: # assignment cycle, stop
186
186
  return on_cycle()
187
187
  if expr.id in params:
188
188
  return on_param(expr.id)
@@ -6,7 +6,7 @@ sanitizer makes it SANITIZED (taint stops), a propagator carries taint through t
6
6
  the result, and a trusted origin or a literal is clean.
7
7
 
8
8
  The point is to let a later layer (P1-04) downgrade a taint finding only when the
9
- value is *provably* not attacker-controlled -- ``classification in SAFE`` -- so
9
+ value is *provably* not attacker-controlled (``classification in SAFE``), so
10
10
  recall is preserved: anything uncertain is UNKNOWN or PARAM, never quietly safe.
11
11
 
12
12
  Two documented precision leans: a bare module-global name (e.g. ``STATIC_DIR``)
@@ -42,7 +42,7 @@ from codejury.resources import TAINT_FILE
42
42
 
43
43
  class Taint(str, Enum):
44
44
  EXTERNAL = "external" # derives from an attacker source, not sanitized
45
- UNKNOWN = "unknown" # an unknown call / access -- cannot prove either way
45
+ UNKNOWN = "unknown" # an unknown call / access; cannot prove either way
46
46
  PARAM = "param" # depends on a parameter; resolve at the call site (cross-file)
47
47
  SANITIZED = "sanitized" # had an external component, but a sanitizer neutralized it
48
48
  TRUSTED = "trusted" # operator/config/global origin
@@ -131,14 +131,14 @@ def taint_of(
131
131
  return Taint.TRUSTED
132
132
  if _callee_in(node, vocab.propagators) or _callee_in(node, vocab.safe_sinks):
133
133
  return _combine([recurse(a) for a in node.args])
134
- return Taint.UNKNOWN # unknown call -- a cross-file hop may resolve it later
134
+ return Taint.UNKNOWN # unknown call; a cross-file hop may resolve it later
135
135
  if isinstance(node, (ast.Attribute, ast.Subscript)):
136
136
  path = access_path(node)
137
137
  if path and _access_in(path, vocab.sources):
138
138
  return Taint.EXTERNAL
139
139
  if path and _access_in(path, vocab.trusted):
140
140
  return Taint.TRUSTED
141
- if access_root(node) in params: # attribute of a parameter -- resolve at the call site
141
+ if access_root(node) in params: # attribute of a parameter; resolve at the call site
142
142
  return on_param(access_root(node))
143
143
  return Taint.UNKNOWN # unknown object attribute (e.g. self.x): not provably safe
144
144
  return Taint.UNKNOWN
@@ -150,7 +150,7 @@ def taint_of(
150
150
  combine=_combine,
151
151
  leaf=leaf,
152
152
  on_param=on_param,
153
- on_global=lambda n: Taint.TRUSTED, # free module global / builtin -- conventionally a constant
153
+ on_global=lambda n: Taint.TRUSTED, # free module global / builtin; conventionally a constant
154
154
  on_constant=lambda: Taint.CONSTANT,
155
155
  on_cycle=lambda: Taint.CONSTANT, # assignment cycle: no new information
156
156
  )
@@ -261,7 +261,7 @@ def worst_sink_taint(content: str, files: dict[str, str], vocab: TaintVocab) ->
261
261
  A "potential sink" is any call that is not a safe sink, sanitizer, or
262
262
  propagator (those are not where injection happens). Each such call's argument
263
263
  taint is classified with the cross-file resolver. A safe sink that consumes
264
- tainted data (e.g. ``json.loads(request.data)``) contributes SANITIZED -- the
264
+ tainted data (e.g. ``json.loads(request.data)``) contributes SANITIZED: the
265
265
  data was handled safely. The worst contribution is returned.
266
266
 
267
267
  ``Taint.UNKNOWN`` when no inspectable sink is found (the artifact may still be
@@ -1,4 +1,4 @@
1
- """Assembly -- build an orchestration from a strategy name and run it over a source.
1
+ """Assembly: build an orchestration from a strategy name and run it over a source.
2
2
 
3
3
  Shared by the CLI and the task layer so the "which agents + which orchestrator"
4
4
  mapping and the per-artifact run loop live in one place.
@@ -88,7 +88,7 @@ def build_orchestration(
88
88
  def provider_tag(provider: Provider) -> str:
89
89
  """A stable short name for a provider (unwrapping RetryProvider) for cache keys,
90
90
  so two providers that accept the same model string do not share cached verdicts."""
91
- return type(getattr(provider, "_inner", provider)).__name__
91
+ return type(getattr(provider, "inner", provider)).__name__
92
92
 
93
93
 
94
94
  def orchestration_descriptor(provider: Provider, strategy: str, model: str, max_tokens: int) -> str:
@@ -1,27 +1,25 @@
1
- """Diff baseline -- report only findings new since a stored baseline report.
1
+ """Diff baseline: report only findings new since a stored baseline report.
2
2
 
3
- The keystone for PR-time noise control: run against a saved baseline report (the
4
- target branch's findings) and keep only the problem observations whose
3
+ The keystone for PR-time noise control: run against a saved baseline report, the
4
+ target branch's findings, and keep only the problem observations whose
5
5
  fingerprint is absent from the baseline, so a review shows what this change
6
- introduced -- not the codebase's pre-existing findings. Paired with --fail-on,
6
+ introduced rather than the codebase's pre-existing findings. Paired with --fail-on,
7
7
  CI then gates on new issues only.
8
8
 
9
- The fingerprint is line-number-tolerant (lines shift between versions): it keys
9
+ The fingerprint is line-number-tolerant because lines shift between versions: it keys
10
10
  on the capability, the kind/severity/status, the matched patterns, and the
11
- normalized evidence snippet -- never the line number. Only problem observations
12
- (Findings, VULNERABLE/PARTIAL Verdicts) are compared and dropped; SECURE /
13
- NOT_PRESENT verdicts and concessions are always kept.
11
+ normalized evidence snippet, never the line number. Only problem observations,
12
+ namely Findings and VULNERABLE/PARTIAL Verdicts, are compared and dropped; SECURE
13
+ and NOT_PRESENT verdicts and concessions are always kept.
14
14
  """
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from codejury.domain.observation import Concession, Finding, Observation, Verdict
18
+ from codejury.domain.observation import Concession, Finding, Observation, Verdict, is_problem
19
19
  from codejury.domain.result import AnalysisResult
20
20
 
21
21
  Results = list[tuple[str, AnalysisResult]]
22
22
 
23
- _PROBLEM_STATUSES = ("VULNERABLE", "PARTIAL")
24
-
25
23
 
26
24
  def finding_key(o: Observation) -> tuple:
27
25
  """A location-tolerant fingerprint for matching a finding across versions."""
@@ -39,13 +37,13 @@ def filter_new(results: Results, baseline: Results) -> tuple[Results, int]:
39
37
 
40
38
  Returns (filtered_results, dropped_count). Non-problem observations are kept.
41
39
  """
42
- seen = {finding_key(o) for _, r in baseline for o in r.observations if _is_problem(o)}
40
+ seen = {finding_key(o) for _, r in baseline for o in r.observations if is_problem(o)}
43
41
  filtered: Results = []
44
42
  dropped = 0
45
43
  for path, result in results:
46
44
  kept: list[Observation] = []
47
45
  for o in result.observations:
48
- if _is_problem(o) and finding_key(o) in seen:
46
+ if is_problem(o) and finding_key(o) in seen:
49
47
  dropped += 1
50
48
  else:
51
49
  kept.append(o)
@@ -53,10 +51,8 @@ def filter_new(results: Results, baseline: Results) -> tuple[Results, int]:
53
51
  return filtered, dropped
54
52
 
55
53
 
56
- def _is_problem(o: Observation) -> bool:
57
- return isinstance(o, Finding) or (isinstance(o, Verdict) and o.status in _PROBLEM_STATUSES)
58
-
59
-
60
54
  def _evidence_sig(o: Observation) -> str:
55
+ # fingerprints only the first evidence snippet, enough to distinguish findings
56
+ # in practice; two findings differing only in a later snippet would collide.
61
57
  evidence = getattr(o, "evidence", [])
62
58
  return " ".join(evidence[0].code.split()) if evidence and evidence[0].code else ""
@@ -280,6 +280,7 @@ def main(argv: list[str] | None = None) -> int:
280
280
  scan_p.add_argument("--no-suppress", action="store_true", help="disable the known-noise suppression filter")
281
281
  scan_p.add_argument("--no-cache", action="store_true", help="bypass the verdict cache (always re-query the model)")
282
282
  scan_p.add_argument("--baseline", default=None, help="a prior JSON report; report only findings new since it")
283
+ scan_p.add_argument("--retries", type=int, default=0, help="provider retry attempts on failure")
283
284
  scan_p.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on", help="exit 1 if a finding at/above this severity is found")
284
285
 
285
286
  run_p = sub.add_parser("run", help="run a named task preset against a unified diff")
@@ -301,9 +302,19 @@ def main(argv: list[str] | None = None) -> int:
301
302
  eval_p.add_argument("--model", default=DEFAULT_MODEL)
302
303
  eval_p.add_argument("--api-base", default=DEFAULT_API_BASE, help="provider base URL (env: CODEJURY_API_BASE)")
303
304
  eval_p.add_argument("--api-key", default=DEFAULT_API_KEY, help="provider API key (env: CODEJURY_API_KEY)")
305
+ eval_p.add_argument("--retries", type=int, default=0, help="provider retry attempts on failure")
304
306
 
305
307
  args = parser.parse_args(argv)
308
+ try:
309
+ return _dispatch(args, parser)
310
+ except Exception as exc:
311
+ # expected failures (missing diff file, provider auth, malformed input) become
312
+ # one stderr line, not a traceback. eval's own message is preserved by command name.
313
+ print(f"{args.command or 'codejury'} failed: {exc}", file=sys.stderr)
314
+ return 1
306
315
 
316
+
317
+ def _dispatch(args, parser) -> int:
307
318
  if args.command == "audit":
308
319
  results = audit(
309
320
  _read_diff(args.diff),
@@ -326,12 +337,15 @@ def main(argv: list[str] | None = None) -> int:
326
337
  capabilities = load_capabilities(args.capabilities)
327
338
  if args.only:
328
339
  wanted = {x.strip() for x in args.only.split(",")}
340
+ unknown = wanted - {c.id for c in capabilities}
341
+ if unknown:
342
+ print(f"warning: --only names unknown capability id(s): {', '.join(sorted(unknown))}", file=sys.stderr)
329
343
  capabilities = [c for c in capabilities if c.id in wanted]
330
344
  extensions = tuple(e if e.startswith(".") else "." + e for e in args.ext.split(","))
331
345
  results = scan(
332
346
  args.directory,
333
347
  capabilities,
334
- provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base),
348
+ provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries),
335
349
  model=args.model,
336
350
  max_tokens=args.max_tokens,
337
351
  strategy=args.orchestrator,
@@ -359,19 +373,13 @@ def main(argv: list[str] | None = None) -> int:
359
373
  return _gate_exit(results, args.fail_on)
360
374
 
361
375
  if args.command == "eval":
362
- try:
363
- report = evaluate(
364
- load_cases(args.dataset, split=args.split),
365
- load_capabilities(args.capabilities),
366
- provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base),
367
- model=args.model,
368
- strategy=args.orchestrator,
369
- )
370
- except Exception as exc:
371
- # e.g. a missing API key surfaces as a provider auth error -- report it
372
- # as one line, not a traceback (audit gets this via the orchestrator).
373
- print(f"eval failed: {exc}")
374
- return 1
376
+ report = evaluate(
377
+ load_cases(args.dataset, split=args.split),
378
+ load_capabilities(args.capabilities),
379
+ provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries),
380
+ model=args.model,
381
+ strategy=args.orchestrator,
382
+ )
375
383
  print(json.dumps(report.to_dict(), indent=2) if args.fmt == "json" else _render_eval(report))
376
384
  return 0
377
385