codejury 0.13.3__tar.gz → 0.14.0__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 (81) hide show
  1. {codejury-0.13.3 → codejury-0.14.0}/PKG-INFO +9 -8
  2. {codejury-0.13.3 → codejury-0.14.0}/README.md +8 -7
  3. {codejury-0.13.3 → codejury-0.14.0}/codejury/cli.py +66 -40
  4. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/PKG-INFO +9 -8
  5. {codejury-0.13.3 → codejury-0.14.0}/pyproject.toml +1 -1
  6. {codejury-0.13.3 → codejury-0.14.0}/tests/test_cli_audit.py +39 -1
  7. {codejury-0.13.3 → codejury-0.14.0}/LICENSE +0 -0
  8. {codejury-0.13.3 → codejury-0.14.0}/codejury/__init__.py +0 -0
  9. {codejury-0.13.3 → codejury-0.14.0}/codejury/analysis/__init__.py +0 -0
  10. {codejury-0.13.3 → codejury-0.14.0}/codejury/analysis/repo_model.py +0 -0
  11. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/agent/full-review.md +0 -0
  12. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/agent/security-review-memory.md +0 -0
  13. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/entrypoints.yaml +0 -0
  14. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/SKILL.md +0 -0
  15. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/business-logic.md +0 -0
  16. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/code-injection.md +0 -0
  17. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/command-injection.md +0 -0
  18. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/cross-site-request-forgery.md +0 -0
  19. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/cross-site-scripting.md +0 -0
  20. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/hardcoded-secrets.md +0 -0
  21. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/http-response-splitting.md +0 -0
  22. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/improper-authentication.md +0 -0
  23. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/information-exposure.md +0 -0
  24. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-cryptography.md +0 -0
  25. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-deserialization.md +0 -0
  26. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
  27. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-transport.md +0 -0
  28. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/jwt-validation.md +0 -0
  29. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/mass-assignment.md +0 -0
  30. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/missing-authorization.md +0 -0
  31. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/open-redirect.md +0 -0
  32. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/path-traversal.md +0 -0
  33. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/race-condition.md +0 -0
  34. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/replay-attack.md +0 -0
  35. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/server-side-request-forgery.md +0 -0
  36. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/server-side-template-injection.md +0 -0
  37. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/session-fixation.md +0 -0
  38. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/sql-injection.md +0 -0
  39. {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/xml-external-entity.md +0 -0
  40. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/__init__.py +0 -0
  41. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/debate.py +0 -0
  42. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/debate_prompts.py +0 -0
  43. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/engine.py +0 -0
  44. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/findings_filter.py +0 -0
  45. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/prompts.py +0 -0
  46. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/report.py +0 -0
  47. {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/rules.py +0 -0
  48. {codejury-0.13.3 → codejury-0.14.0}/codejury/domain/__init__.py +0 -0
  49. {codejury-0.13.3 → codejury-0.14.0}/codejury/domain/finding.py +0 -0
  50. {codejury-0.13.3 → codejury-0.14.0}/codejury/fullreview/__init__.py +0 -0
  51. {codejury-0.13.3 → codejury-0.14.0}/codejury/fullreview/scaffold.py +0 -0
  52. {codejury-0.13.3 → codejury-0.14.0}/codejury/infrastructure/__init__.py +0 -0
  53. {codejury-0.13.3 → codejury-0.14.0}/codejury/infrastructure/json_parse.py +0 -0
  54. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/__init__.py +0 -0
  55. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/anthropic.py +0 -0
  56. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/base.py +0 -0
  57. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/factory.py +0 -0
  58. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/litellm.py +0 -0
  59. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/mock.py +0 -0
  60. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/openai.py +0 -0
  61. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/openai_format.py +0 -0
  62. {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/retry.py +0 -0
  63. {codejury-0.13.3 → codejury-0.14.0}/codejury/resources.py +0 -0
  64. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/SOURCES.txt +0 -0
  65. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/dependency_links.txt +0 -0
  66. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/entry_points.txt +0 -0
  67. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/requires.txt +0 -0
  68. {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/top_level.txt +0 -0
  69. {codejury-0.13.3 → codejury-0.14.0}/setup.cfg +0 -0
  70. {codejury-0.13.3 → codejury-0.14.0}/tests/test_anthropic_provider.py +0 -0
  71. {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_debate.py +0 -0
  72. {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_engine.py +0 -0
  73. {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_report.py +0 -0
  74. {codejury-0.13.3 → codejury-0.14.0}/tests/test_full_review_scaffold.py +0 -0
  75. {codejury-0.13.3 → codejury-0.14.0}/tests/test_json_parse.py +0 -0
  76. {codejury-0.13.3 → codejury-0.14.0}/tests/test_litellm_provider.py +0 -0
  77. {codejury-0.13.3 → codejury-0.14.0}/tests/test_openai_format.py +0 -0
  78. {codejury-0.13.3 → codejury-0.14.0}/tests/test_openai_provider.py +0 -0
  79. {codejury-0.13.3 → codejury-0.14.0}/tests/test_repo_model.py +0 -0
  80. {codejury-0.13.3 → codejury-0.14.0}/tests/test_retry_provider.py +0 -0
  81. {codejury-0.13.3 → codejury-0.14.0}/tests/test_rules.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.13.3
3
+ Version: 0.14.0
4
4
  Summary: AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -57,29 +57,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
57
57
 
58
58
  ```bash
59
59
  # audit a diff file
60
- codejury audit --diff-file changes.diff
60
+ codejury review diff --diff-file changes.diff
61
61
 
62
62
  # audit a git range in a repo
63
- codejury audit --repo /path/to/app --git-range origin/main...HEAD
63
+ codejury review diff --repo /path/to/app --git-range origin/main...HEAD
64
64
 
65
65
  # from stdin
66
- git diff HEAD~1 | codejury audit
66
+ git diff HEAD~1 | codejury review diff
67
67
 
68
68
  # adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
69
- codejury audit --diff-file changes.diff --mode adversarial
69
+ codejury review diff --diff-file changes.diff --mode adversarial
70
70
 
71
71
  # CI gate + SARIF
72
- codejury audit --diff-file changes.diff --format sarif --fail-on high
72
+ codejury review diff --diff-file changes.diff --format sarif --fail-on high
73
73
  ```
74
74
 
75
75
  Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
76
76
  `CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
77
- `codejury dry-run` exercises the engine with a mock provider and no key.
77
+ `codejury review diff --dry-run` exercises the engine with a mock provider and
78
+ no key (it uses a built-in demo diff when you do not pass one).
78
79
 
79
80
  ## Whole-repo review
80
81
 
81
82
  ```bash
82
- codejury full-review /path/to/your/repo
83
+ codejury review repo /path/to/your/repo
83
84
  ```
84
85
 
85
86
  This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
@@ -26,29 +26,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
26
26
 
27
27
  ```bash
28
28
  # audit a diff file
29
- codejury audit --diff-file changes.diff
29
+ codejury review diff --diff-file changes.diff
30
30
 
31
31
  # audit a git range in a repo
32
- codejury audit --repo /path/to/app --git-range origin/main...HEAD
32
+ codejury review diff --repo /path/to/app --git-range origin/main...HEAD
33
33
 
34
34
  # from stdin
35
- git diff HEAD~1 | codejury audit
35
+ git diff HEAD~1 | codejury review diff
36
36
 
37
37
  # adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
38
- codejury audit --diff-file changes.diff --mode adversarial
38
+ codejury review diff --diff-file changes.diff --mode adversarial
39
39
 
40
40
  # CI gate + SARIF
41
- codejury audit --diff-file changes.diff --format sarif --fail-on high
41
+ codejury review diff --diff-file changes.diff --format sarif --fail-on high
42
42
  ```
43
43
 
44
44
  Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
45
45
  `CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
46
- `codejury dry-run` exercises the engine with a mock provider and no key.
46
+ `codejury review diff --dry-run` exercises the engine with a mock provider and
47
+ no key (it uses a built-in demo diff when you do not pass one).
47
48
 
48
49
  ## Whole-repo review
49
50
 
50
51
  ```bash
51
- codejury full-review /path/to/your/repo
52
+ codejury review repo /path/to/your/repo
52
53
  ```
53
54
 
54
55
  This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
@@ -19,6 +19,7 @@ import sys
19
19
 
20
20
  import dataclasses
21
21
 
22
+ from codejury import __version__
22
23
  from codejury.diff.debate import AdversarialAuditRunner
23
24
  from codejury.diff.engine import AuditRunner
24
25
  from codejury.diff.findings_filter import FindingsFilter
@@ -82,11 +83,13 @@ def audit_diff(
82
83
  finder_model: str | None = None,
83
84
  challenger_model: str | None = None,
84
85
  judge_model: str | None = None,
86
+ exclude_paths: tuple[str, ...] = (),
85
87
  ) -> tuple[list[Finding], list[tuple[Finding, str]]]:
86
88
  """Audit a diff and return (kept findings, dropped (finding, reason)).
87
89
 
88
90
  A diff over the size budget is audited one file at a time so it does not
89
- overflow the context. Finding categories are normalized to the rule-id set."""
91
+ overflow the context. Finding categories are normalized to the rule-id set.
92
+ ``exclude_paths`` are operator-supplied path substrings to drop."""
90
93
  def _run_one(d: str) -> list[Finding]:
91
94
  if mode == "adversarial":
92
95
  return AdversarialAuditRunner(
@@ -104,7 +107,7 @@ def audit_diff(
104
107
  allowed = set(allowed_categories())
105
108
  findings = [dataclasses.replace(f, category=normalize_category(f.category, allowed)) for f in findings]
106
109
  if filter_findings:
107
- return FindingsFilter().filter(findings)
110
+ return FindingsFilter(exclude_paths=exclude_paths).filter(findings)
108
111
  return findings, []
109
112
 
110
113
 
@@ -124,54 +127,82 @@ def _dry_run_diff() -> str:
124
127
  return "+++ b/app.py\n@@ -0,0 +1 @@\n+cursor.execute('SELECT * FROM u WHERE n=' + name)\n"
125
128
 
126
129
 
130
+ # canned reply for `review diff --dry-run`: a mock provider returns this so the
131
+ # pipeline runs end to end with no key and no backend call
132
+ _MOCK_REPLY = (
133
+ '{"findings": [{"file": "app.py", "line": 1, "severity": "HIGH", '
134
+ '"category": "sql_injection", "description": "[mock] no backend called", '
135
+ '"confidence": 0.9}]}'
136
+ )
137
+
138
+
139
+ def _add_audit_args(p) -> None:
140
+ """The diff-audit flags for `review diff`."""
141
+ p.add_argument("--diff-file", default=None, help="unified diff file (default: read stdin)")
142
+ p.add_argument("--repo", default=None, help="repo path for --git-range")
143
+ p.add_argument("--git-range", default=None, help="git range to diff, e.g. origin/main...HEAD")
144
+ p.add_argument("--dry-run", action="store_true",
145
+ help="run the engine with a mock provider and no key (a built-in demo diff if none is given)")
146
+ p.add_argument("--exclude", action="append", default=None, metavar="PATH",
147
+ help="drop findings whose file path contains this substring (repeatable)")
148
+ p.add_argument("--mode", choices=("standard", "adversarial"), default="standard")
149
+ p.add_argument("--rounds", type=int, default=3, help="adversarial only: debate rounds")
150
+ p.add_argument("--provider", choices=PROVIDERS, default="anthropic")
151
+ p.add_argument("--model", default=DEFAULT_MODEL)
152
+ p.add_argument("--finder-model", default=DEFAULT_FINDER_MODEL, help="adversarial only: finder role model (default: --model)")
153
+ p.add_argument("--challenger-model", default=DEFAULT_CHALLENGER_MODEL, help="adversarial only: challenger role model")
154
+ p.add_argument("--judge-model", default=DEFAULT_JUDGE_MODEL, help="adversarial only: judge role model")
155
+ p.add_argument("--api-base", default=DEFAULT_API_BASE)
156
+ p.add_argument("--api-key", default=DEFAULT_API_KEY)
157
+ p.add_argument("--retries", type=int, default=2, help="provider retry attempts on transient failure")
158
+ p.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
159
+ p.add_argument("--no-filter", action="store_true", help="skip the false-positive filter")
160
+ p.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on")
161
+
162
+
127
163
  def main(argv: list[str] | None = None) -> int:
128
164
  parser = argparse.ArgumentParser(prog="codejury")
165
+ parser.add_argument("--version", action="version", version=f"codejury {__version__}")
129
166
  sub = parser.add_subparsers(dest="command")
130
167
 
131
- sub.add_parser("dry-run", help="run the diff engine with a mock provider, no key")
132
-
133
- a = sub.add_parser("audit", help="audit a unified diff for security findings")
134
- a.add_argument("--diff-file", default=None, help="unified diff file (default: read stdin)")
135
- a.add_argument("--repo", default=None, help="repo path for --git-range")
136
- a.add_argument("--git-range", default=None, help="git range to diff, e.g. origin/main...HEAD")
137
- a.add_argument("--mode", choices=("standard", "adversarial"), default="standard")
138
- a.add_argument("--rounds", type=int, default=3, help="adversarial debate rounds")
139
- a.add_argument("--provider", choices=PROVIDERS, default="anthropic")
140
- a.add_argument("--model", default=DEFAULT_MODEL)
141
- a.add_argument("--finder-model", default=DEFAULT_FINDER_MODEL, help="adversarial: finder role model (default: --model)")
142
- a.add_argument("--challenger-model", default=DEFAULT_CHALLENGER_MODEL, help="adversarial: challenger role model")
143
- a.add_argument("--judge-model", default=DEFAULT_JUDGE_MODEL, help="adversarial: judge role model")
144
- a.add_argument("--api-base", default=DEFAULT_API_BASE)
145
- a.add_argument("--api-key", default=DEFAULT_API_KEY)
146
- a.add_argument("--retries", type=int, default=2, help="provider retry attempts on transient failure")
147
- a.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
148
- a.add_argument("--no-filter", action="store_true", help="skip the false-positive filter")
149
- a.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on")
150
-
151
- fr = sub.add_parser("full-review", help="scaffold a whole-repo review for an interactive agent")
152
- fr.add_argument("directory", help="target repository to review")
153
- fr.add_argument("--workspace", default="codejury-review", help="where to create the review workspace")
168
+ review = sub.add_parser("review", help="review code for security findings")
169
+ rsub = review.add_subparsers(dest="scope")
170
+ _add_audit_args(rsub.add_parser("diff", help="audit a unified diff (the coded engine)"))
171
+ repo = rsub.add_parser("repo", help="scaffold a whole-repo review for an interactive agent")
172
+ repo.add_argument("directory", help="target repository to review")
173
+ repo.add_argument("--workspace", default="codejury-review", help="where to create the review workspace")
154
174
 
155
175
  args = parser.parse_args(argv)
156
176
  try:
157
177
  return _dispatch(args, parser)
158
178
  except Exception as exc:
159
- print(f"{args.command or 'codejury'} failed: {exc}", file=sys.stderr)
179
+ label = getattr(args, "command", None) or "codejury"
180
+ print(f"{label} failed: {exc}", file=sys.stderr)
160
181
  return 1
161
182
 
162
183
 
163
184
  def _dispatch(args, parser) -> int:
164
- if args.command == "audit":
165
- provider = make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries)
185
+ scope = getattr(args, "scope", None)
186
+ if args.command == "review" and scope == "diff":
187
+ if args.dry_run:
188
+ provider = MockProvider(default=_MOCK_REPLY)
189
+ model = "mock"
190
+ # zero-config smoke test: fall back to a built-in demo diff when none is supplied
191
+ diff = _read_diff(args) if (args.diff_file or args.git_range) else _dry_run_diff()
192
+ else:
193
+ provider = make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries)
194
+ model = args.model
195
+ diff = _read_diff(args)
166
196
  kept, _ = audit_diff(
167
- _read_diff(args), provider=provider, model=args.model,
197
+ diff, provider=provider, model=model,
168
198
  mode=args.mode, max_rounds=args.rounds, filter_findings=not args.no_filter,
169
199
  finder_model=args.finder_model, challenger_model=args.challenger_model, judge_model=args.judge_model,
200
+ exclude_paths=tuple(args.exclude or ()),
170
201
  )
171
202
  print(render(args.fmt, kept))
172
203
  return 1 if gate(kept, args.fail_on) else 0
173
204
 
174
- if args.command == "full-review":
205
+ if args.command == "review" and scope == "repo":
175
206
  res = scaffold(args.directory, args.workspace)
176
207
  print(f"Workspace: {res.workspace}", file=sys.stderr)
177
208
  print(f"Seeded {res.entrypoints} entrypoints into {res.workspace}/api/_entrypoints.md", file=sys.stderr)
@@ -180,16 +211,11 @@ def _dispatch(args, parser) -> int:
180
211
  print(res.methodology)
181
212
  return 0
182
213
 
183
- if args.command in (None, "dry-run"):
184
- kept, _ = audit_diff(
185
- _dry_run_diff(),
186
- provider=MockProvider(default='{"findings": [{"file": "app.py", "line": 1, "severity": "HIGH", '
187
- '"category": "sql_injection", "description": "[mock] no backend called", '
188
- '"confidence": 0.9}]}'),
189
- model="mock",
190
- )
191
- print(render("text", kept))
192
- return 0
214
+ if args.command == "review": # no scope given
215
+ print("usage: codejury review {diff,repo} ...", file=sys.stderr)
216
+ print(" diff audit a unified diff for security findings", file=sys.stderr)
217
+ print(" repo scaffold a whole-repo review for an interactive agent", file=sys.stderr)
218
+ return 1
193
219
 
194
220
  parser.print_help()
195
221
  return 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.13.3
3
+ Version: 0.14.0
4
4
  Summary: AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -57,29 +57,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
57
57
 
58
58
  ```bash
59
59
  # audit a diff file
60
- codejury audit --diff-file changes.diff
60
+ codejury review diff --diff-file changes.diff
61
61
 
62
62
  # audit a git range in a repo
63
- codejury audit --repo /path/to/app --git-range origin/main...HEAD
63
+ codejury review diff --repo /path/to/app --git-range origin/main...HEAD
64
64
 
65
65
  # from stdin
66
- git diff HEAD~1 | codejury audit
66
+ git diff HEAD~1 | codejury review diff
67
67
 
68
68
  # adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
69
- codejury audit --diff-file changes.diff --mode adversarial
69
+ codejury review diff --diff-file changes.diff --mode adversarial
70
70
 
71
71
  # CI gate + SARIF
72
- codejury audit --diff-file changes.diff --format sarif --fail-on high
72
+ codejury review diff --diff-file changes.diff --format sarif --fail-on high
73
73
  ```
74
74
 
75
75
  Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
76
76
  `CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
77
- `codejury dry-run` exercises the engine with a mock provider and no key.
77
+ `codejury review diff --dry-run` exercises the engine with a mock provider and
78
+ no key (it uses a built-in demo diff when you do not pass one).
78
79
 
79
80
  ## Whole-repo review
80
81
 
81
82
  ```bash
82
- codejury full-review /path/to/your/repo
83
+ codejury review repo /path/to/your/repo
83
84
  ```
84
85
 
85
86
  This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codejury"
3
- version = "0.13.3"
3
+ version = "0.14.0"
4
4
  description = "AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -5,7 +5,9 @@ big PR does not overflow the model context and silently truncate the reply; the
5
5
  per-file findings are then de-duplicated.
6
6
  """
7
7
 
8
- from codejury.cli import _dedup_findings, _split_diff_by_file, audit_diff
8
+ import pytest
9
+
10
+ from codejury.cli import _dedup_findings, _split_diff_by_file, audit_diff, main
9
11
  from codejury.domain.finding import Finding
10
12
  from codejury.providers.mock import MockProvider
11
13
 
@@ -43,3 +45,39 @@ def test_large_diff_is_audited_per_file(monkeypatch):
43
45
  assert len(provider.calls) == 2
44
46
  # category normalized onto the rule-id set
45
47
  assert all(f.category == "sql-injection" for f in kept)
48
+
49
+
50
+ def test_audit_diff_honors_exclude_paths():
51
+ resp = ('{"findings": [{"file": "vendor/lib.py", "line": 1, "severity": "HIGH", '
52
+ '"category": "sql_injection", "description": "x", "confidence": 0.9}]}')
53
+ kept, dropped = audit_diff(
54
+ _FILE_A, provider=MockProvider(default=resp), model="mock", exclude_paths=("vendor/",)
55
+ )
56
+ assert kept == [] and dropped and "excluded path" in dropped[0][1]
57
+
58
+
59
+ # --- CLI surface ---
60
+
61
+ def test_version_flag_exits_zero(capsys):
62
+ with pytest.raises(SystemExit) as exc:
63
+ main(["--version"])
64
+ assert exc.value.code == 0
65
+ assert "codejury" in capsys.readouterr().out
66
+
67
+
68
+ def test_review_diff_dry_run_is_zero_config(capsys):
69
+ # no diff input, no key: the built-in demo diff runs through the mock provider
70
+ rc = main(["review", "diff", "--dry-run"])
71
+ assert rc == 0
72
+ assert "sql-injection" in capsys.readouterr().out
73
+
74
+
75
+ def test_review_diff_dry_run_respects_exclude(capsys):
76
+ rc = main(["review", "diff", "--dry-run", "--exclude", "app.py"])
77
+ assert rc == 0
78
+ assert "no findings" in capsys.readouterr().out
79
+
80
+
81
+ def test_old_audit_command_is_gone(capsys):
82
+ with pytest.raises(SystemExit): # argparse rejects the removed command
83
+ main(["audit", "--dry-run"])
File without changes
File without changes
File without changes