open-code-review-toolkit 0.2.0__tar.gz → 0.3.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 (67) hide show
  1. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/PKG-INFO +11 -2
  2. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/README.md +10 -1
  3. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/pyproject.toml +7 -13
  4. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/_version.py +2 -2
  5. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/cli.py +10 -2
  6. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/manifests.py +27 -15
  7. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/repo.py +4 -2
  8. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/gitlab.py +14 -10
  9. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/markers.py +6 -4
  10. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/workflow.py +5 -4
  11. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/preflight.py +1 -1
  12. open_code_review_toolkit-0.2.0/CHANGELOG.md +0 -50
  13. open_code_review_toolkit-0.2.0/CONTRIBUTING.md +0 -14
  14. open_code_review_toolkit-0.2.0/SECURITY.md +0 -13
  15. open_code_review_toolkit-0.2.0/changelog.d/README.md +0 -3
  16. open_code_review_toolkit-0.2.0/docs/codex/AGENT_EXECUTION_PITFALLS.md +0 -44
  17. open_code_review_toolkit-0.2.0/docs/codex/TASKS_BACKLOG.md +0 -87
  18. open_code_review_toolkit-0.2.0/docs/configuration.md +0 -67
  19. open_code_review_toolkit-0.2.0/docs/development.md +0 -19
  20. open_code_review_toolkit-0.2.0/docs/engineering/project_principles.md +0 -30
  21. open_code_review_toolkit-0.2.0/docs/gitlab.md +0 -31
  22. open_code_review_toolkit-0.2.0/docs/operations.md +0 -83
  23. open_code_review_toolkit-0.2.0/docs/release.md +0 -49
  24. open_code_review_toolkit-0.2.0/docs/security.md +0 -29
  25. open_code_review_toolkit-0.2.0/examples/gitlab/ocr-review.gitlab-ci.yml +0 -84
  26. open_code_review_toolkit-0.2.0/examples/gitlab/rules.json +0 -33
  27. open_code_review_toolkit-0.2.0/tests/__init__.py +0 -1
  28. open_code_review_toolkit-0.2.0/tests/support.py +0 -85
  29. open_code_review_toolkit-0.2.0/tests/test_cli.py +0 -39
  30. open_code_review_toolkit-0.2.0/tests/test_common_helpers.py +0 -391
  31. open_code_review_toolkit-0.2.0/tests/test_context_helpers.py +0 -1946
  32. open_code_review_toolkit-0.2.0/tests/test_install_local_artifact.py +0 -67
  33. open_code_review_toolkit-0.2.0/tests/test_operations_docs.py +0 -58
  34. open_code_review_toolkit-0.2.0/tests/test_posting_helpers.py +0 -1757
  35. open_code_review_toolkit-0.2.0/tests/test_quality_script.py +0 -12
  36. open_code_review_toolkit-0.2.0/tests/test_release_authorization.py +0 -107
  37. open_code_review_toolkit-0.2.0/tests/test_release_notes.py +0 -46
  38. open_code_review_toolkit-0.2.0/tests/test_release_process_docs.py +0 -30
  39. open_code_review_toolkit-0.2.0/tests/test_runtime_helpers.py +0 -880
  40. open_code_review_toolkit-0.2.0/tests/test_testpypi_preview.py +0 -238
  41. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/.gitignore +0 -0
  42. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/LICENSE +0 -0
  43. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/__init__.py +0 -0
  44. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/common/__init__.py +0 -0
  45. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/common/language.py +0 -0
  46. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/common/markdown.py +0 -0
  47. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/common/redaction.py +0 -0
  48. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/config_writer.py +0 -0
  49. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/configure.py +0 -0
  50. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/__init__.py +0 -0
  51. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/__main__.py +0 -0
  52. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/ansible.py +0 -0
  53. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/categorize.py +0 -0
  54. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/instructions.py +0 -0
  55. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/planner.py +0 -0
  56. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/render.py +0 -0
  57. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/context/settings.py +0 -0
  58. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/mcp_config.py +0 -0
  59. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/__init__.py +0 -0
  60. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/__main__.py +0 -0
  61. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/comments.py +0 -0
  62. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/formatting.py +0 -0
  63. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/payloads.py +0 -0
  64. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/result.py +0 -0
  65. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/settings.py +0 -0
  66. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/posting/snapshot.py +0 -0
  67. {open_code_review_toolkit-0.2.0 → open_code_review_toolkit-0.3.0}/src/ocr_toolkit/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-code-review-toolkit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Unofficial GitLab CI integration layer for Open Code Review
5
5
  Project-URL: Homepage, https://github.com/xeonvs/open-code-review-toolkit
6
6
  Project-URL: Repository, https://github.com/xeonvs/open-code-review-toolkit
@@ -243,7 +243,8 @@ ocr --version
243
243
  ocr-ci --help
244
244
  ```
245
245
 
246
- The current compatibility target is OCR `1.7.14`. CI should pin the release and verify its published checksum before execution.
246
+ The current compatibility target is OCR `1.7.17`. CI should pin the release and verify its published checksum before execution.
247
+ The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
247
248
  Review output defaults to English. Set `OCR_REVIEW_LANGUAGE=Russian` to use Russian consistently in both OCR configuration and generated review context.
248
249
 
249
250
  Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
@@ -256,6 +257,14 @@ Suppression uses both the GitLab diff position and a stable finding fingerprint,
256
257
 
257
258
  Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the context generator supplies them to OCR only when the current merge request is not changing that file. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) for the entry format, inline marker convention, security boundary, and limitations.
258
259
 
260
+ ## Project development
261
+
262
+ The project is evolving from bounded background generation toward a shared Repository Evidence Engine: one deterministic evidence model will support both a compact OCR bootstrap and a built-in read-only MCP server. Development is ordered by outcomes and dependencies rather than speculative dates.
263
+
264
+ - [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
265
+ - [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
266
+ - [Backlog](docs/codex/TASKS_BACKLOG.md) - inactive implementation-ready work; active execution remains in `PLANS.md`.
267
+
259
268
  ## GitLab CI quick start
260
269
 
261
270
  1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
@@ -15,7 +15,8 @@ ocr --version
15
15
  ocr-ci --help
16
16
  ```
17
17
 
18
- The current compatibility target is OCR `1.7.14`. CI should pin the release and verify its published checksum before execution.
18
+ The current compatibility target is OCR `1.7.17`. CI should pin the release and verify its published checksum before execution.
19
+ The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
19
20
  Review output defaults to English. Set `OCR_REVIEW_LANGUAGE=Russian` to use Russian consistently in both OCR configuration and generated review context.
20
21
 
21
22
  Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
@@ -28,6 +29,14 @@ Suppression uses both the GitLab diff position and a stable finding fingerprint,
28
29
 
29
30
  Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the context generator supplies them to OCR only when the current merge request is not changing that file. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) for the entry format, inline marker convention, security boundary, and limitations.
30
31
 
32
+ ## Project development
33
+
34
+ The project is evolving from bounded background generation toward a shared Repository Evidence Engine: one deterministic evidence model will support both a compact OCR bootstrap and a built-in read-only MCP server. Development is ordered by outcomes and dependencies rather than speculative dates.
35
+
36
+ - [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
37
+ - [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
38
+ - [Backlog](docs/codex/TASKS_BACKLOG.md) - inactive implementation-ready work; active execution remains in `PLANS.md`.
39
+
31
40
  ## GitLab CI quick start
32
41
 
33
42
  1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
@@ -38,6 +38,7 @@ ocr-ci = "ocr_toolkit.cli:main"
38
38
 
39
39
  [dependency-groups]
40
40
  dev = [
41
+ "bandit==1.9.4",
41
42
  "build>=1.2.2",
42
43
  "coverage[toml]>=7.6",
43
44
  "hatch-vcs>=0.4",
@@ -60,19 +61,7 @@ version-file = "src/ocr_toolkit/_version.py"
60
61
  packages = ["src/ocr_toolkit"]
61
62
 
62
63
  [tool.hatch.build.targets.sdist]
63
- include = [
64
- "/src",
65
- "/tests",
66
- "/examples",
67
- "/docs",
68
- "/changelog.d",
69
- "/README.md",
70
- "/CONTRIBUTING.md",
71
- "/SECURITY.md",
72
- "/LICENSE",
73
- "/CHANGELOG.md",
74
- "/pyproject.toml",
75
- ]
64
+ only-include = ["src", "README.md", "LICENSE", "pyproject.toml"]
76
65
 
77
66
  [tool.hatch.version.raw-options]
78
67
  fallback_version = "0.3.0.dev0"
@@ -101,6 +90,11 @@ exclude = ["src/ocr_toolkit/_version.py"]
101
90
  warn_unreachable = false
102
91
  disable_error_code = ["arg-type", "assignment", "import-not-found", "index", "name-defined", "no-any-return", "no-redef", "union-attr", "unused-ignore", "var-annotated"]
103
92
 
93
+ [tool.bandit]
94
+ exclude_dirs = ["tests"]
95
+ severity = "medium"
96
+ confidence = "medium"
97
+
104
98
  [tool.pytest.ini_options]
105
99
  addopts = "--strict-config --strict-markers"
106
100
  testpaths = ["tests"]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0'
22
- __version_tuple__ = version_tuple = (0, 2, 0)
21
+ __version__ = version = '0.3.0'
22
+ __version_tuple__ = version_tuple = (0, 3, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -33,9 +33,17 @@ def build_parser() -> argparse.ArgumentParser:
33
33
 
34
34
  post_parser = subparsers.add_parser("post", help="Publish an OCR result artifact to GitLab.")
35
35
  post_parser.add_argument(
36
- "--result", default="/tmp/ocr-result.json", help="OCR JSON result path."
36
+ # The public GitLab example writes this fixed path inside an isolated CI job.
37
+ "--result",
38
+ default="/tmp/ocr-result.json", # nosec B108
39
+ help="OCR JSON result path.",
40
+ )
41
+ post_parser.add_argument(
42
+ # The file contains only the current job's OCR stderr and is never shared.
43
+ "--stderr",
44
+ default="/tmp/ocr-stderr.log", # nosec B108
45
+ help="OCR stderr log path.",
37
46
  )
38
- post_parser.add_argument("--stderr", default="/tmp/ocr-stderr.log", help="OCR stderr log path.")
39
47
  return parser
40
48
 
41
49
 
@@ -196,9 +196,9 @@ def parse_pyproject(path: Path) -> dict[str, Any]:
196
196
 
197
197
  try:
198
198
  try:
199
- import tomllib
199
+ import tomllib # type: ignore[import-untyped]
200
200
  except ModuleNotFoundError:
201
- import tomli as tomllib # type: ignore[import-not-found]
201
+ import tomli as tomllib
202
202
 
203
203
  data = tomllib.loads(text)
204
204
  except ModuleNotFoundError:
@@ -209,9 +209,12 @@ def parse_pyproject(path: Path) -> dict[str, Any]:
209
209
  if not isinstance(data, dict):
210
210
  return {"present": True, "parse_error": JSON_OBJECT_PARSE_ERROR}
211
211
 
212
- project = data.get("project") if isinstance(data.get("project"), dict) else {}
213
- tool = data.get("tool") if isinstance(data.get("tool"), dict) else {}
214
- poetry = tool.get("poetry") if isinstance(tool.get("poetry"), dict) else {}
212
+ raw_project = data.get("project")
213
+ raw_tool = data.get("tool")
214
+ project: dict[Any, Any] = raw_project if isinstance(raw_project, dict) else {}
215
+ tool: dict[Any, Any] = raw_tool if isinstance(raw_tool, dict) else {}
216
+ raw_poetry = tool.get("poetry")
217
+ poetry: dict[Any, Any] = raw_poetry if isinstance(raw_poetry, dict) else {}
215
218
 
216
219
  dependencies: list[str] = []
217
220
  if isinstance(project.get("dependencies"), list):
@@ -233,12 +236,14 @@ def parse_pyproject(path: Path) -> dict[str, Any]:
233
236
  f"group.{group_name}: {redact_url_userinfo(str(dep))}" for dep in group_deps
234
237
  )
235
238
 
236
- poetry_deps = poetry.get("dependencies") if isinstance(poetry.get("dependencies"), dict) else {}
239
+ raw_poetry_deps = poetry.get("dependencies")
240
+ poetry_deps: dict[Any, Any] = raw_poetry_deps if isinstance(raw_poetry_deps, dict) else {}
237
241
  for name, version in poetry_deps.items():
238
242
  if str(name).lower() != "python":
239
243
  dependencies.append(f"{name}: {redact_url_userinfo(str(version))}")
240
244
 
241
- poetry_groups = poetry.get("group") if isinstance(poetry.get("group"), dict) else {}
245
+ raw_poetry_groups = poetry.get("group")
246
+ poetry_groups: dict[Any, Any] = raw_poetry_groups if isinstance(raw_poetry_groups, dict) else {}
242
247
  for group_name, group_value in poetry_groups.items():
243
248
  if not isinstance(group_value, dict):
244
249
  continue
@@ -354,10 +359,14 @@ def parse_composer_json(path: Path) -> dict[str, Any]:
354
359
  if not isinstance(data, dict):
355
360
  return {"present": True, "parse_error": JSON_OBJECT_PARSE_ERROR}
356
361
 
357
- require = data.get("require") if isinstance(data.get("require"), dict) else {}
358
- require_dev = data.get("require-dev") if isinstance(data.get("require-dev"), dict) else {}
359
- config = data.get("config") if isinstance(data.get("config"), dict) else {}
360
- platform = config.get("platform") if isinstance(config.get("platform"), dict) else {}
362
+ raw_require = data.get("require")
363
+ raw_require_dev = data.get("require-dev")
364
+ raw_config = data.get("config")
365
+ require: dict[Any, Any] = raw_require if isinstance(raw_require, dict) else {}
366
+ require_dev: dict[Any, Any] = raw_require_dev if isinstance(raw_require_dev, dict) else {}
367
+ config: dict[Any, Any] = raw_config if isinstance(raw_config, dict) else {}
368
+ raw_platform = config.get("platform")
369
+ platform: dict[Any, Any] = raw_platform if isinstance(raw_platform, dict) else {}
361
370
 
362
371
  platform_items, platform_omitted = limited_manifest_items(
363
372
  [f"{name}: {version}" for name, version in platform.items()]
@@ -433,10 +442,13 @@ def parse_package_json(path: Path) -> dict[str, Any]:
433
442
  if not isinstance(data, dict):
434
443
  return {"present": True, "parse_error": JSON_OBJECT_PARSE_ERROR}
435
444
 
436
- engines = data.get("engines") if isinstance(data.get("engines"), dict) else {}
437
- dependencies = data.get("dependencies") if isinstance(data.get("dependencies"), dict) else {}
438
- dev_dependencies = (
439
- data.get("devDependencies") if isinstance(data.get("devDependencies"), dict) else {}
445
+ raw_engines = data.get("engines")
446
+ raw_dependencies = data.get("dependencies")
447
+ raw_dev_dependencies = data.get("devDependencies")
448
+ engines: dict[Any, Any] = raw_engines if isinstance(raw_engines, dict) else {}
449
+ dependencies: dict[Any, Any] = raw_dependencies if isinstance(raw_dependencies, dict) else {}
450
+ dev_dependencies: dict[Any, Any] = (
451
+ raw_dev_dependencies if isinstance(raw_dev_dependencies, dict) else {}
440
452
  )
441
453
 
442
454
  engines_items, engines_omitted = limited_manifest_items(
@@ -162,11 +162,13 @@ def resolve_output_path(value: str) -> Path | None:
162
162
  requested = Path(value)
163
163
  logical_output = requested if requested.is_absolute() else root_lexical / requested
164
164
  logical_output = logical_output.absolute()
165
- temp_lexical = Path(tempfile.gettempdir()).absolute()
165
+ # tempfile supplies the platform temp root; containment and symlink checks follow.
166
+ temp_lexical = Path(tempfile.gettempdir()).absolute() # nosec B108
166
167
  allowed_roots = [(root_lexical, root), (root, root)]
167
168
  temp_resolved = temp_lexical.resolve()
168
169
  allowed_roots.extend([(temp_lexical, temp_resolved), (temp_resolved, temp_resolved)])
169
- tmp_path = Path("/tmp")
170
+ # POSIX /tmp may be a distinct lexical alias (for example, /private/tmp).
171
+ tmp_path = Path("/tmp") # nosec B108
170
172
  if tmp_path.exists():
171
173
  tmp_lexical = tmp_path.absolute()
172
174
  tmp_resolved = tmp_path.resolve()
@@ -125,7 +125,7 @@ def _open_gitlab_request(request: urllib.request.Request) -> Any:
125
125
  def _read_limited_response(response: Any) -> bytes:
126
126
  """Read a GitLab response body and fail when it exceeds the success budget."""
127
127
 
128
- body = response.read(MAX_API_RESPONSE_BODY_BYTES)
128
+ body: bytes = response.read(MAX_API_RESPONSE_BODY_BYTES)
129
129
  if len(body) >= MAX_API_RESPONSE_BODY_BYTES and response.read(1):
130
130
  raise GitLabResponseTooLarge(f"GitLab response exceeds {MAX_API_RESPONSE_BODY_BYTES} bytes")
131
131
  return body
@@ -323,11 +323,13 @@ def fetch_current_user_id(server_url: str, api_token: str, auth_header: str) ->
323
323
  return None
324
324
 
325
325
  raw_user_id = result.get("id")
326
- try:
327
- return int(raw_user_id)
328
- except (TypeError, ValueError):
329
- print_user_id_failure_banner("GET /user response has no valid id field")
330
- return None
326
+ if isinstance(raw_user_id, (str, int, float)) and not isinstance(raw_user_id, bool):
327
+ try:
328
+ return int(raw_user_id)
329
+ except ValueError:
330
+ pass
331
+ print_user_id_failure_banner("GET /user response has no valid id field")
332
+ return None
331
333
 
332
334
 
333
335
  def load_gitlab_config() -> GitLabConfig | None:
@@ -481,10 +483,12 @@ def draft_note_id(value: Any) -> int | None:
481
483
  if isinstance(raw_id, bool):
482
484
  return None
483
485
 
484
- try:
485
- return int(raw_id)
486
- except (TypeError, ValueError):
487
- return None
486
+ if isinstance(raw_id, (str, int, float)) and not isinstance(raw_id, bool):
487
+ try:
488
+ return int(raw_id)
489
+ except ValueError:
490
+ pass
491
+ return None
488
492
 
489
493
 
490
494
  def created_draft_note(response: Any, context: str) -> DraftNoteCreation | None:
@@ -240,10 +240,12 @@ def author_id_from_note(note: dict[str, Any]) -> int | None:
240
240
  if isinstance(author, dict):
241
241
  raw_author_id = author.get("id")
242
242
 
243
- try:
244
- return int(raw_author_id)
245
- except (TypeError, ValueError):
246
- return None
243
+ if isinstance(raw_author_id, (str, int, float)) and not isinstance(raw_author_id, bool):
244
+ try:
245
+ return int(raw_author_id)
246
+ except ValueError:
247
+ pass
248
+ return None
247
249
 
248
250
 
249
251
  def is_diff_note(note: dict[str, Any]) -> bool:
@@ -131,7 +131,7 @@ def changed_new_lines(
131
131
  timeout=15,
132
132
  )
133
133
  except (OSError, subprocess.SubprocessError):
134
- lines = set()
134
+ lines: set[int] = set()
135
135
  if cache is not None:
136
136
  cache[cache_key] = lines
137
137
  return lines
@@ -217,7 +217,7 @@ def head_file_lines(
217
217
  size_result.returncode != 0
218
218
  or int(size_result.stdout.strip() or "0") > MAX_REMAP_FILE_BYTES
219
219
  ):
220
- lines = []
220
+ lines: list[tuple[int, str]] = []
221
221
  if cache is not None:
222
222
  cache[cache_key] = lines
223
223
  return lines
@@ -827,8 +827,9 @@ def main(argv: list[str] | None = None) -> int:
827
827
  """CLI entrypoint."""
828
828
 
829
829
  args = argv if argv is not None else sys.argv[1:]
830
- result_path = Path(args[0]) if len(args) >= 1 else Path("/tmp/ocr-result.json")
831
- stderr_path = Path(args[1]) if len(args) >= 2 else Path("/tmp/ocr-stderr.log")
830
+ # Defaults are per-job files in the isolated CI container; callers can override both.
831
+ result_path = Path(args[0]) if len(args) >= 1 else Path("/tmp/ocr-result.json") # nosec B108
832
+ stderr_path = Path(args[1]) if len(args) >= 2 else Path("/tmp/ocr-stderr.log") # nosec B108
832
833
 
833
834
  config = load_gitlab_config()
834
835
  if config is None:
@@ -24,7 +24,7 @@ DEFAULT_REQUEST_HEADERS = {
24
24
  "Accept": "application/json",
25
25
  "User-Agent": "open-code-review-ci-preflight/1.0",
26
26
  }
27
- EXPECTED_OCR_VERSION = "1.7.14"
27
+ EXPECTED_OCR_VERSION = "1.7.17"
28
28
 
29
29
 
30
30
  class PreflightError(Exception):
@@ -1,50 +0,0 @@
1
- ## 0.2.0 - 2026-07-21
2
-
3
- ### Features
4
-
5
- - Target Open Code Review 1.7.14 in preflight validation and the checksum-pinned GitLab CI example. ([#11](https://github.com/xeonvs/open-code-review-toolkit/issues/11))
6
- - Replace the ambiguous `/ocr keep` and `/ocr skip` discussion replies with `/ocr resolve` and `/ocr suppress`, preserve human-owned deduplication, and document the complete GitLab review lifecycle for developers and CI operators. ([#8](https://github.com/xeonvs/open-code-review-toolkit/issues/8))
7
-
8
- ### Bug fixes
9
-
10
- - Allow stable release verification to coexist with previously published development builds of the same base version on TestPyPI. ([#10](https://github.com/xeonvs/open-code-review-toolkit/issues/10))
11
- - Treat ordinary merged pull requests as a successful no-op in the production release workflow while keeping release-branch authorization fail-closed. ([#7](https://github.com/xeonvs/open-code-review-toolkit/issues/7))
12
-
13
- ### Security
14
-
15
- - Mark every source-distribution smoke install as hash-required while retaining the no-dependency boundary, and document the single-maintainer security posture and Scorecard triage policy. ([#6](https://github.com/xeonvs/open-code-review-toolkit/issues/6))
16
-
17
- ### Documentation
18
-
19
- - Reduce the routine Ubuntu CI matrix to the supported Python 3.10 and 3.14 endpoints, matching the macOS matrix. ([#10](https://github.com/xeonvs/open-code-review-toolkit/issues/10))
20
- - Document accepted project decisions, their optional `ocr-accept` marker convention, and the guard that prevents a merge request from whitelisting its own findings. ([#11](https://github.com/xeonvs/open-code-review-toolkit/issues/11))
21
-
22
-
23
- ## 0.1.0 - 2026-07-20
24
-
25
- ### Features
26
-
27
- - Publish one deterministic, checksum-verified TestPyPI development build after every merge into `main`, with bounded registry downloads and idempotent reruns. ([#4](https://github.com/xeonvs/open-code-review-toolkit/pull/4))
28
- - Target Open Code Review 1.7.13 in preflight validation and the pinned GitLab CI example. ([#4](https://github.com/xeonvs/open-code-review-toolkit/pull/4))
29
- - Support and continuously test Python 3.14 while retaining Python 3.10-3.13 compatibility. ([#4](https://github.com/xeonvs/open-code-review-toolkit/pull/4))
30
- - Publish reproducible 0.1.0 distributions to TestPyPI and PyPI with exact hash verification, provenance attestations, and immutable GitHub Release assets.
31
- - Introduce the standalone `ocr-ci` toolkit with safe context generation, GitLab posting, runtime configuration, MCP configuration, and preflight checks.
32
-
33
- ### Bug fixes
34
-
35
- - Bind production release smoke tests to the exact reviewed wheel and sdist hashes, with bounded HTTPS downloads from TestPyPI and PyPI. ([#4](https://github.com/xeonvs/open-code-review-toolkit/pull/4))
36
- - Use `OCR_REVIEW_LANGUAGE` as the single safe language setting for OCR configuration and generated review context, with English as the default and Russian as an explicit option. ([#4](https://github.com/xeonvs/open-code-review-toolkit/pull/4))
37
- - Preserve bounded context and version discovery with a 7,950-character ceiling, improve provider billing classification, and prevent cross-file remapping of findings that already name a path. ([#3](https://github.com/xeonvs/open-code-review-toolkit/pull/3))
38
-
39
- ### Security
40
-
41
- - Require secure credential endpoints, block unsafe GitLab redirects, redact secret-shaped environment values, and reduce GitHub Actions credential persistence and permissions. ([#3](https://github.com/xeonvs/open-code-review-toolkit/pull/3))
42
-
43
- ### Documentation
44
-
45
- - Document the checksum-verified TestPyPI prerelease path used before the public stable release. ([#2](https://github.com/xeonvs/open-code-review-toolkit/pull/2))
46
-
47
-
48
- # Changelog
49
-
50
- Changes for each release are assembled from `changelog.d/` by Towncrier.
@@ -1,14 +0,0 @@
1
- # Contributing
2
-
3
- Thank you for improving Open Code Review Toolkit. Keep changes focused, add regression tests for behavior changes, and use synthetic examples only.
4
-
5
- 1. Create a branch from `main`.
6
- 2. Install the locked development environment with `uv sync --frozen`.
7
- 3. Update `PLANS.md` for substantial work.
8
- 4. Add a Towncrier fragment for user-visible changes.
9
- 5. Run the checks in [docs/development.md](docs/development.md).
10
- 6. Open a pull request; protected `main` accepts changes only through green pull requests.
11
-
12
- Every non-release merge publishes a `0.2.0.devN` development build to TestPyPI. Stable releases use a repository-owned `release/vX.Y.Z` branch and an exact `Release vX.Y.Z` pull-request title; merging that reviewed PR authorizes the automated TestPyPI, PyPI, and GitHub Release chain.
13
-
14
- Do not include real credentials, provider payloads, internal hosts, or private repository details. Security reports should follow [SECURITY.md](SECURITY.md), not public issues.
@@ -1,13 +0,0 @@
1
- # Security Policy
2
-
3
- ## Supported versions
4
-
5
- Before 1.0, only the newest published release receives security fixes.
6
-
7
- ## Reporting a vulnerability
8
-
9
- Use [GitHub private vulnerability reporting](https://github.com/xeonvs/open-code-review-toolkit/security/advisories/new) for this repository. Do not open a public issue or attach live credentials, proprietary source, or production provider responses. Include a minimal synthetic reproducer, impact, affected version, and suggested mitigation when available.
10
-
11
- Maintainers will acknowledge a report within five business days, privately validate severity and affected versions, and coordinate disclosure only after a fix or documented mitigation is available. Critical fixes are released as soon as practical; reporters receive status updates at least every ten business days while investigation remains open.
12
-
13
- The runtime trust model and deployment guidance are documented in [docs/security.md](docs/security.md).
@@ -1,3 +0,0 @@
1
- # Changelog fragments
2
-
3
- Every user-visible 0.x change needs one fragment named `<issue>.<type>.md`, where type is `feature`, `bugfix`, `security`, or `doc`. Use a numeric GitHub issue or pull-request identifier so generated links remain valid.
@@ -1,44 +0,0 @@
1
- # Agent Execution Pitfalls
2
-
3
- ## Closing a public-contract change after only the feature merge
4
-
5
- **Failure mode:** A feature changes a public command or integration contract, its pull request merges, and a TestPyPI `.devN` build succeeds. The plan is then marked completed even though stable PyPI users still receive the old behavior.
6
-
7
- **Why it happens:** Implementation, preview publication, and stable delivery are treated as separate mental tasks even when the user asked for one outcome. SCM-derived versions also make the source tree appear ready for the next version without proving that a stable tag or package exists.
8
-
9
- **Required prevention:**
10
-
11
- 1. Classify the work at plan start and write the target stable version into `PLANS.md`.
12
- 2. Treat feature merge and TestPyPI `.devN` verification as intermediate receipts.
13
- 3. When release is required, prepare the version/changelog release PR immediately after the development build is verified.
14
- 4. Keep the objective active until the release workflow publishes and independent readback confirms PyPI, TestPyPI, the signed tag, immutable GitHub Release, hashes, attestations, and supported-Python installs.
15
- 5. If publication is intentionally deferred, record who deferred it, why, and the exact command or PR needed to resume.
16
-
17
- **Closure question:** "Can a user installing from production PyPI obtain the promised behavior now?" If not, the stable-release objective is not complete.
18
-
19
- This note records recurring execution mistake patterns discovered during real work. Record generalized lessons, not one-off complaints.
20
-
21
- ## Planning And Context Discipline
22
-
23
- - Do not leave durable scope, source boundaries, or resume state only in chat; keep the active plan current.
24
- - Do not treat a local working specification as publishable documentation; translate its public requirements and keep private audit criteria outside tracked files.
25
- - Do not silently reduce extraction scope because the source is large; split it into coherent, validated subsystem slices.
26
- - Do not commit completed work while the active plan still says `planned` or `in_progress`.
27
-
28
- ## Source And Privacy Boundaries
29
-
30
- - Inventory tracked source explicitly and avoid broad copy commands that could include ignored or untracked files.
31
- - Do not turn one-time private marker criteria into a tracked denylist or test fixture.
32
- - Do not use real provider payloads, hosts, repositories, or credentials in public fixtures.
33
-
34
- ## Tooling And Validation Hygiene
35
-
36
- - Prefer the narrowest reproducer before broad reruns.
37
- - Verify both UTF-8 byte limits and Python character limits when changing note formatting.
38
- - Treat tests, lint, typing, artifact checks, install smoke, privacy scans, and source-integrity checks as distinct gates.
39
- - Pin third-party Actions by full commit SHA and keep readable version comments beside the pin.
40
-
41
- ## Learning Loop
42
-
43
- - Promote a repeated stable lesson into `docs/engineering/project_principles.md`.
44
- - Record actionable future work in `docs/codex/TASKS_BACKLOG.md` only when it has an activation trigger and next safe action.
@@ -1,87 +0,0 @@
1
- # Tasks Backlog
2
-
3
- This file tracks future work that is not active. Active extraction work is owned by `PLANS.md`.
4
-
5
- ## Backlog Item: Native fuzzing campaign
6
-
7
- Status: parked
8
- Priority: medium
9
-
10
- ### Activation Trigger
11
-
12
- - A supported Python fuzzing backend is selected with reproducible local execution, bounded CI resources, and corpus ownership.
13
-
14
- ### Goal
15
-
16
- Fuzz the untrusted-input boundaries with meaningful targets for result normalization, context rendering, GitLab payload parsing, and registry metadata validation. Integrate a public fuzzing service only after the targets demonstrate useful coverage and stable crash minimization.
17
-
18
- ### Next Safe Action
19
-
20
- 1. Compare Atheris with property-based alternatives across Python 3.10-3.14 and design a small synthetic seed corpus without adding a runtime dependency.
21
-
22
- ### Exit Criteria
23
-
24
- - Native targets run locally and in bounded CI, retain minimized synthetic regressions, publish no repository or provider secrets, and are recognized by the selected fuzzing service.
25
-
26
- ## Backlog Item: OpenSSF Best Practices registration
27
-
28
- Status: owner action
29
- Priority: low
30
-
31
- ### Activation Trigger
32
-
33
- - The owner is ready to authenticate at `bestpractices.dev` and attest every passing-level criterion truthfully.
34
-
35
- ### Goal
36
-
37
- Register the public repository for an OpenSSF Best Practices badge without guessing owner-only governance or project-usage answers.
38
-
39
- ### Next Safe Action
40
-
41
- 1. Complete the passing-level questionnaire with evidence links to the public repository and leave unsupported criteria unmet.
42
-
43
- ### Exit Criteria
44
-
45
- - The public badge record exists, all answers have current evidence, and the README displays only the earned status.
46
-
47
- ## Backlog Item: Additional provider adapters
48
-
49
- Status: parked
50
- Priority: low
51
-
52
- ### Activation Trigger
53
-
54
- - A provider-neutral core has shipped and a concrete provider integration has an owner and testable API contract.
55
-
56
- ### Goal
57
-
58
- Add another code-hosting adapter without weakening the provider-neutral core or GitLab behavior.
59
-
60
- ### Next Safe Action
61
-
62
- 1. Write an adapter contract proposal based on the shipped GitLab boundary and validate it against synthetic fixtures.
63
-
64
- ### Exit Criteria
65
-
66
- - The new adapter has isolated tests, documentation, and no provider-specific leakage into core modules.
67
-
68
- ## Backlog Item: File-based user configuration
69
-
70
- Status: parked
71
- Priority: low
72
-
73
- ### Activation Trigger
74
-
75
- - Environment-only configuration becomes a demonstrated usability constraint after v0.1.
76
-
77
- ### Goal
78
-
79
- Evaluate a versioned user configuration file without silently changing environment precedence or secret handling.
80
-
81
- ### Next Safe Action
82
-
83
- 1. Draft a schema and threat model; do not implement until compatibility and migration behavior are agreed.
84
-
85
- ### Exit Criteria
86
-
87
- - Schema, precedence, migration, secret handling, and validation behavior are documented and tested.
@@ -1,67 +0,0 @@
1
- # Environment configuration
2
-
3
- Open Code Review Toolkit uses environment variables only in v0.1. Empty values are generally treated as absent. Exact defaults and safety caps are enforced by the runtime modules.
4
-
5
- ## OCR and LLM
6
-
7
- | Variable | Purpose |
8
- | --- | --- |
9
- | `OCR_LLM_URL` | OpenAI-compatible chat or responses endpoint. |
10
- | `OCR_LLM_MODEL` | Exact model identifier passed to OCR. |
11
- | `OCR_REVIEW_LANGUAGE` | Single review language used by OCR config and generated context. Defaults to `English`; set `Russian` for Russian review output. |
12
- | `OCR_LLM_TOKEN` | LLM credential. Never written into generated context. |
13
- | `OCR_LLM_AUTH_HEADER` | Optional authorization header name; defaults to `Authorization`. |
14
- | `OCR_LLM_EXTRA_HEADERS` | Optional JSON object of additional string headers. |
15
- | `OCR_LLM_SUPPORTS_FUNCTION_CALLING` | Boolean capability flag. |
16
- | `OCR_LLM_SUPPORTS_REASONING` | Boolean capability flag. |
17
- | `OCR_LLM_MAX_TOKENS` | Optional positive completion-token limit. |
18
- | `OCR_LLM_VALIDATE_MODEL` | `true`, `false`, or `auto`; defaults to `false`. |
19
- | `OCR_LLM_MODELS_URL` | Explicit `/models` metadata URL. |
20
- | `OCR_LLM_ALLOWED_MODELS` | Optional comma-separated offline allowlist for `auto` validation. |
21
- | `OCR_CONFIG_PATH` | Override the OCR JSON config path. |
22
-
23
- ## MCP
24
-
25
- | Variable | Purpose |
26
- | --- | --- |
27
- | `OCR_MCP_SERVERS_JSON` | JSON object mapping server names to command, arguments, environment, and optional tool allowlists. |
28
- | `OCR_MCP_REPLACE` | Replace configured MCP servers when true; otherwise merge by server name. |
29
-
30
- MCP commands run as child processes of OCR. Treat their executable, arguments, environment, output, and tool access as privileged configuration.
31
-
32
- ## GitLab CI inputs
33
-
34
- Posting requires `GITLAB_API_TOKEN`, `CI_SERVER_URL`, `CI_PROJECT_ID`, and `CI_MERGE_REQUEST_IID`. Inline discussions additionally use GitLab diff refs and merge-request source/base SHA variables. `CI_COMMIT_SHA` remains distinct from the merge-request source SHA and is never assumed to identify the reviewed branch head.
35
-
36
- ## Posting controls
37
-
38
- `OCR_POST_MODE`, `OCR_STRICT_POSTING`, `OCR_EXIT_CODE`, `OCR_MAX_POST_COMMENTS`, `OCR_MAX_RESULT_BYTES`, and `OCR_POST_ERROR_DETAILS` control write behavior and bounded error reporting. Invalid numeric or boolean values fail closed or fall back to conservative defaults as documented in command output. Human replies to bot-created discussions prevent automated ownership actions on that discussion.
39
-
40
- ## Context controls
41
-
42
- The `OCR_CONTEXT_*` family bounds files, bytes, changed paths, instructions, manifest content, dependency output, and generated background size. `OCR_BACKGROUND_MAX_CHARS` defaults to and is capped at `7950`; `OCR_BACKGROUND_MAX_BYTES` independently enforces the UTF-8 byte budget. The default output is `.review-context/dependencies.md`. The generator rejects symlink escapes and prunes common vendor/build directories.
43
-
44
- ### Accepted project decisions
45
-
46
- Use `.opencodereview/accepted-decisions.md` for a reviewed, project-wide decision that OCR would otherwise report repeatedly. Each entry should have a stable slug, a concise rationale, an explicit scope, and an inline marker that ties the decision to the relevant code or configuration:
47
-
48
- ```markdown
49
- ## generated-client-timeout
50
-
51
- The generated client keeps the provider's 90-second timeout so regenerated
52
- code remains reproducible. Do not report that timeout in `src/client/generated.py`.
53
-
54
- Look for `# ocr-accept: generated-client-timeout` at the configured value.
55
- ```
56
-
57
- ```python
58
- REQUEST_TIMEOUT = 90 # ocr-accept: generated-client-timeout
59
- ```
60
-
61
- `ocr-accept` is a human-readable convention, not a source-code parser or blanket linter suppression. The complete, byte-bounded Markdown file is sanitized, redacted, and included under `Accepted project decisions` in the generated review background; OCR is instructed not to raise matching findings. Keep the rationale narrow and name the affected paths or behavior so unrelated findings remain reviewable.
62
-
63
- The decision file must already exist on the target branch and pass normal review. If the current merge request changes `.opencodereview/accepted-decisions.md`, or changed-file discovery fails, the toolkit omits all accepted decisions for that run to prevent self-whitelisting. A decision reduces repeated model findings but is not a deterministic static-analysis exemption: reviewers should still use `/ocr suppress` or `/ocr resolve` for a concrete GitLab discussion, and should update or remove stale decisions when the underlying tradeoff changes.
64
-
65
- Use the default `OCR_POST_MODE=draft` for normal CI so all current notes are created as drafts before they are published and replaceable notes from the previous run are removed. Draft publication is sequential rather than atomic; the previous review is preserved unless every current draft publishes. Set `OCR_STRICT_POSTING=true` when the review job is a required merge gate; keep the default `false` only for advisory pipelines where GitLab posting availability must not block the pipeline. Reviewer commands and the complete repeated-run contract are documented in [GitLab review operations](operations.md).
66
-
67
- Run `ocr-ci --help` and each subcommand's help for command arguments. Secret values are redacted from operational error text.