open-code-review-toolkit 0.4.2__tar.gz → 0.4.3__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.
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/PKG-INFO +2 -2
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/README.md +1 -1
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/formatting.py +15 -2
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/workflow.py +108 -33
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/preflight.py +1 -1
- open_code_review_toolkit-0.4.3/src/ocr_toolkit/result_contract.py +271 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/review_runner.py +25 -23
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/.gitignore +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/LICENSE +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/pyproject.toml +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/cli.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/ansible.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/ansible_requirements.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/artifacts.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/collect.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/collectors.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/composer_manifests.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/go_manifests.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/javascript_manifests.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/manifest_model.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/mcp.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/model.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/project.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/python_manifests.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/store.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/ocr_result.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/providers/gitlab.py +0 -0
- {open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/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.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
@@ -241,7 +241,7 @@ ocr --version
|
|
|
241
241
|
ocr-ci --help
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
-
The current compatibility target is OCR `1.8.
|
|
244
|
+
The current compatibility target is OCR `1.8.6`. CI should pin the release and verify its published checksum before execution.
|
|
245
245
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
246
246
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
247
247
|
|
|
@@ -15,7 +15,7 @@ ocr --version
|
|
|
15
15
|
ocr-ci --help
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The current compatibility target is OCR `1.8.
|
|
18
|
+
The current compatibility target is OCR `1.8.6`. CI should pin the release and verify its published checksum before execution.
|
|
19
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.
|
|
20
20
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
21
21
|
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/_version.py
RENAMED
|
@@ -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.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
21
|
+
__version__ = version = '0.4.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -689,6 +689,7 @@ def format_reviewer_guide(
|
|
|
689
689
|
omitted_count: int,
|
|
690
690
|
*,
|
|
691
691
|
outcome_status: str = "success",
|
|
692
|
+
coverage_summary: str = "",
|
|
692
693
|
) -> str:
|
|
693
694
|
"""Build a bounded reviewer guide from already published OCR findings."""
|
|
694
695
|
|
|
@@ -715,10 +716,12 @@ def format_reviewer_guide(
|
|
|
715
716
|
f"- Estimated effort to review: {effort}/5",
|
|
716
717
|
]
|
|
717
718
|
)
|
|
718
|
-
if outcome_status
|
|
719
|
+
if outcome_status in {"budget_exceeded", "partial"}:
|
|
719
720
|
lines.append(
|
|
720
|
-
"- ⚠️ Review scope: OCR
|
|
721
|
+
"- ⚠️ Review scope: OCR reported partial coverage; treat all findings as a partial review."
|
|
721
722
|
)
|
|
723
|
+
if coverage_summary:
|
|
724
|
+
lines.append(f"- Review coverage: {coverage_summary}")
|
|
722
725
|
|
|
723
726
|
if not security_comments and omitted_count:
|
|
724
727
|
lines.append(
|
|
@@ -771,6 +774,7 @@ def summarize_result(
|
|
|
771
774
|
mr_head_sha: str = "",
|
|
772
775
|
outcome_status: str = "success",
|
|
773
776
|
outcome_message: str = "",
|
|
777
|
+
coverage_summary: str = "",
|
|
774
778
|
emoji: bool | None = None,
|
|
775
779
|
) -> str:
|
|
776
780
|
"""Build a compact summary note for the MR."""
|
|
@@ -782,6 +786,10 @@ def summarize_result(
|
|
|
782
786
|
"completed_with_warnings": "⚠️",
|
|
783
787
|
"completed_with_errors": "❌",
|
|
784
788
|
"budget_exceeded": "⚠️",
|
|
789
|
+
"clean": "✅",
|
|
790
|
+
"warning": "⚠️",
|
|
791
|
+
"partial": "⚠️",
|
|
792
|
+
"failed": "❌",
|
|
785
793
|
}
|
|
786
794
|
marker = f"{status_markers.get(outcome_status, '❌')} " if use_emoji else ""
|
|
787
795
|
safe_message = neutralize_quick_actions(
|
|
@@ -790,6 +798,8 @@ def summarize_result(
|
|
|
790
798
|
if not safe_message:
|
|
791
799
|
if outcome_status == "budget_exceeded":
|
|
792
800
|
safe_message = "Review stopped after reaching its token budget; findings are partial."
|
|
801
|
+
elif outcome_status == "partial":
|
|
802
|
+
safe_message = "Review completed with partial coverage; findings are incomplete."
|
|
793
803
|
else:
|
|
794
804
|
safe_message = f"Found {total} issue(s)." if total else "No issues found."
|
|
795
805
|
lines = [
|
|
@@ -808,6 +818,9 @@ def summarize_result(
|
|
|
808
818
|
if mr_head_sha and mr_head_sha != reviewed_sha:
|
|
809
819
|
lines.append(f"- MR head SHA: {_inline_code(mr_head_sha)}")
|
|
810
820
|
|
|
821
|
+
if coverage_summary and not reviewer_guide:
|
|
822
|
+
lines.append(f"- {coverage_summary}")
|
|
823
|
+
|
|
811
824
|
severity_counts = format_metadata_counts(comments, "severity", OCR_FINDING_SEVERITY_ORDER)
|
|
812
825
|
if severity_counts:
|
|
813
826
|
lines.append(f"- severity tags: {severity_counts}")
|
|
@@ -56,6 +56,7 @@ from ocr_toolkit.posting.snapshot import (
|
|
|
56
56
|
publish_failure_exit,
|
|
57
57
|
rollback_current_run_comments,
|
|
58
58
|
)
|
|
59
|
+
from ocr_toolkit.result_contract import OcrResultContractError, ReviewOutcome, parse_result_outcome
|
|
59
60
|
|
|
60
61
|
# Kept as a module-level compatibility seam for tests and external monkey-patching.
|
|
61
62
|
post_review_note = gitlab_api.post_review_note
|
|
@@ -386,31 +387,16 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
386
387
|
tool_calls_summary = format_tool_calls_summary(result.get("tool_calls"))
|
|
387
388
|
mcp_usage_summary = format_mcp_usage_summary(result.get(TOOLKIT_RESULT_KEY))
|
|
388
389
|
token_usage_summary = format_token_usage_summary(result)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
"completed_with_warnings",
|
|
394
|
-
"completed_with_errors",
|
|
395
|
-
"budget_exceeded",
|
|
396
|
-
}
|
|
397
|
-
if status not in allowed_statuses:
|
|
398
|
-
return invalid_ocr_schema_exit(config, "field 'status' is unsupported")
|
|
390
|
+
try:
|
|
391
|
+
outcome = parse_result_outcome(result)
|
|
392
|
+
except OcrResultContractError as exc:
|
|
393
|
+
return invalid_ocr_schema_exit(config, str(exc))
|
|
399
394
|
outcome_message = clean_text(result.get("message"))
|
|
400
395
|
|
|
401
396
|
if not isinstance(comments_value, list):
|
|
402
397
|
return invalid_ocr_schema_exit(config, "field 'comments' must be a list")
|
|
403
398
|
if not isinstance(warnings_value, list):
|
|
404
399
|
return invalid_ocr_schema_exit(config, "field 'warnings' must be a list")
|
|
405
|
-
summary_value = result.get("summary")
|
|
406
|
-
summary_budget_exceeded = (
|
|
407
|
-
isinstance(summary_value, dict) and summary_value.get("budget_exceeded") is True
|
|
408
|
-
)
|
|
409
|
-
if (status == "budget_exceeded") != summary_budget_exceeded:
|
|
410
|
-
return invalid_ocr_schema_exit(
|
|
411
|
-
config, "fields 'status' and 'summary.budget_exceeded' disagree"
|
|
412
|
-
)
|
|
413
|
-
|
|
414
400
|
comments: list[dict[str, Any]] = []
|
|
415
401
|
for index, comment in enumerate(comments_value):
|
|
416
402
|
if not isinstance(comment, dict):
|
|
@@ -418,6 +404,17 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
418
404
|
comments.append(comment)
|
|
419
405
|
|
|
420
406
|
warnings = warnings_value
|
|
407
|
+
if outcome.kind == "failed":
|
|
408
|
+
return post_manifest_failure(
|
|
409
|
+
config,
|
|
410
|
+
outcome,
|
|
411
|
+
outcome_message,
|
|
412
|
+
warnings,
|
|
413
|
+
tool_calls_summary=tool_calls_summary,
|
|
414
|
+
mcp_usage_summary=mcp_usage_summary,
|
|
415
|
+
token_usage_summary=token_usage_summary,
|
|
416
|
+
)
|
|
417
|
+
|
|
421
418
|
billing_warnings = llm_billing_failure_warnings(warnings)
|
|
422
419
|
if billing_warnings:
|
|
423
420
|
print(
|
|
@@ -464,7 +461,12 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
464
461
|
comments = comments[:publish_limit]
|
|
465
462
|
|
|
466
463
|
emoji = post_emoji()
|
|
467
|
-
reviewer_guide = format_reviewer_guide(
|
|
464
|
+
reviewer_guide = format_reviewer_guide(
|
|
465
|
+
comments,
|
|
466
|
+
omitted_count,
|
|
467
|
+
outcome_status=outcome.kind,
|
|
468
|
+
coverage_summary=outcome.coverage_summary,
|
|
469
|
+
)
|
|
468
470
|
|
|
469
471
|
if publishable_comment_count == 0:
|
|
470
472
|
raw_message = clean_text(result.get("message"))
|
|
@@ -477,21 +479,20 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
477
479
|
)
|
|
478
480
|
message = neutralize_quick_actions(redact_sensitive(raw_message))
|
|
479
481
|
marker = {
|
|
480
|
-
"
|
|
482
|
+
"clean": "✅",
|
|
483
|
+
"warning": "⚠️",
|
|
484
|
+
"partial": "⚠️",
|
|
481
485
|
"skipped": "ℹ️", # noqa: RUF001 - intentional information emoji
|
|
482
|
-
|
|
483
|
-
"completed_with_errors": "❌",
|
|
484
|
-
"budget_exceeded": "⚠️",
|
|
485
|
-
}[status]
|
|
486
|
+
}[outcome.kind]
|
|
486
487
|
fallback_message = (
|
|
487
488
|
"No review comments generated. No issues found."
|
|
488
|
-
if
|
|
489
|
+
if outcome.kind == "clean"
|
|
489
490
|
else (
|
|
490
491
|
"No supported files changed."
|
|
491
|
-
if
|
|
492
|
+
if outcome.kind == "skipped"
|
|
492
493
|
else (
|
|
493
494
|
"Review stopped after reaching its token budget; this is a partial result."
|
|
494
|
-
if
|
|
495
|
+
if outcome.budget_exceeded
|
|
495
496
|
else "Review did not complete cleanly."
|
|
496
497
|
)
|
|
497
498
|
)
|
|
@@ -515,6 +516,8 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
515
516
|
body += f"\n{mcp_usage_summary}"
|
|
516
517
|
if token_usage_summary:
|
|
517
518
|
body += f"\n{token_usage_summary}"
|
|
519
|
+
if outcome.coverage_summary:
|
|
520
|
+
body += f"\n{outcome.coverage_summary}"
|
|
518
521
|
if reviewer_guide:
|
|
519
522
|
body += f"\n\n{reviewer_guide}"
|
|
520
523
|
response = post_review_note_bounded(
|
|
@@ -528,8 +531,7 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
528
531
|
return posting_failure_exit(config, previous_bot_comment_refs, draft_note_ids)
|
|
529
532
|
if not finalize_posting(config, draft_note_ids):
|
|
530
533
|
return publish_failure_exit(config, draft_note_ids)
|
|
531
|
-
|
|
532
|
-
resolve_requested_discussions(config, previous_bot_comment_refs)
|
|
534
|
+
finalize_previous_review_state(config, previous_bot_comment_refs, outcome)
|
|
533
535
|
return 0
|
|
534
536
|
|
|
535
537
|
refs = get_diff_refs(config)
|
|
@@ -665,8 +667,9 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
665
667
|
fallback_reasons=fallback_reasons,
|
|
666
668
|
reviewed_sha=reviewed_sha(),
|
|
667
669
|
mr_head_sha=mr_head_sha(),
|
|
668
|
-
outcome_status=
|
|
670
|
+
outcome_status=outcome.kind,
|
|
669
671
|
outcome_message=outcome_message,
|
|
672
|
+
coverage_summary=outcome.coverage_summary,
|
|
670
673
|
emoji=emoji,
|
|
671
674
|
),
|
|
672
675
|
draft_note_ids,
|
|
@@ -679,8 +682,7 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
679
682
|
if not finalize_posting(config, draft_note_ids):
|
|
680
683
|
return publish_failure_exit(config, draft_note_ids)
|
|
681
684
|
|
|
682
|
-
|
|
683
|
-
resolve_requested_discussions(config, previous_bot_comment_refs)
|
|
685
|
+
finalize_previous_review_state(config, previous_bot_comment_refs, outcome)
|
|
684
686
|
|
|
685
687
|
print(
|
|
686
688
|
f"Posted OCR comments: mode={post_mode()}, inline={inline_count}, "
|
|
@@ -690,6 +692,20 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
690
692
|
return 0
|
|
691
693
|
|
|
692
694
|
|
|
695
|
+
def finalize_previous_review_state(
|
|
696
|
+
config: GitLabConfig,
|
|
697
|
+
previous_refs: BotCommentRefs,
|
|
698
|
+
outcome: ReviewOutcome,
|
|
699
|
+
) -> None:
|
|
700
|
+
"""Replace prior notes only after a complete outcome; preserve them for partial coverage."""
|
|
701
|
+
|
|
702
|
+
if outcome.kind == "partial":
|
|
703
|
+
print("OCR coverage is partial; preserving previous review comments until a complete run.")
|
|
704
|
+
else:
|
|
705
|
+
delete_previous_bot_comments_if_collected(config, previous_refs)
|
|
706
|
+
resolve_requested_discussions(config, previous_refs)
|
|
707
|
+
|
|
708
|
+
|
|
693
709
|
def resolve_requested_discussions(
|
|
694
710
|
config: GitLabConfig, previous_refs: BotCommentRefs | None
|
|
695
711
|
) -> None:
|
|
@@ -737,6 +753,65 @@ def invalid_ocr_schema_exit(
|
|
|
737
753
|
return 1 if strict_posting() else 0
|
|
738
754
|
|
|
739
755
|
|
|
756
|
+
def post_manifest_failure(
|
|
757
|
+
config: GitLabConfig,
|
|
758
|
+
outcome: ReviewOutcome,
|
|
759
|
+
message: str,
|
|
760
|
+
warnings: Sequence[Any],
|
|
761
|
+
*,
|
|
762
|
+
tool_calls_summary: str = "",
|
|
763
|
+
mcp_usage_summary: str = "",
|
|
764
|
+
token_usage_summary: str = "",
|
|
765
|
+
) -> int:
|
|
766
|
+
"""Post a manifest-declared run failure while preserving prior review notes."""
|
|
767
|
+
|
|
768
|
+
draft_note_ids: list[int] = []
|
|
769
|
+
safe_message = compact_escaped_text(
|
|
770
|
+
neutralize_quick_actions(redact_sensitive(message)),
|
|
771
|
+
1_200,
|
|
772
|
+
)
|
|
773
|
+
body_parts = [
|
|
774
|
+
"OCR reported that the review run failed before it could produce a complete result.",
|
|
775
|
+
"",
|
|
776
|
+
"- Normal review comments were not published.",
|
|
777
|
+
"- Previous OCR review comments were preserved.",
|
|
778
|
+
]
|
|
779
|
+
if safe_message:
|
|
780
|
+
body_parts.append(f"- Outcome: {safe_message}")
|
|
781
|
+
if outcome.coverage_summary:
|
|
782
|
+
body_parts.append(f"- {outcome.coverage_summary}")
|
|
783
|
+
|
|
784
|
+
warning_items: list[str] = []
|
|
785
|
+
for warning in warnings[:10]:
|
|
786
|
+
safe_warning = compact_escaped_text(
|
|
787
|
+
neutralize_quick_actions(redact_sensitive(ocr_warning_text(warning))),
|
|
788
|
+
1_200,
|
|
789
|
+
)
|
|
790
|
+
if safe_warning:
|
|
791
|
+
warning_items.append(f"- {safe_warning}")
|
|
792
|
+
if warning_items:
|
|
793
|
+
body_parts.extend(["", "**Warnings:**", *warning_items])
|
|
794
|
+
if tool_calls_summary:
|
|
795
|
+
body_parts.extend(["", tool_calls_summary])
|
|
796
|
+
if mcp_usage_summary:
|
|
797
|
+
body_parts.append(mcp_usage_summary)
|
|
798
|
+
if token_usage_summary:
|
|
799
|
+
body_parts.append(token_usage_summary)
|
|
800
|
+
|
|
801
|
+
response = post_review_note_bounded(
|
|
802
|
+
config,
|
|
803
|
+
"**Open Code Review failed result**",
|
|
804
|
+
"\n".join(body_parts),
|
|
805
|
+
draft_note_ids,
|
|
806
|
+
)
|
|
807
|
+
if response is None:
|
|
808
|
+
print("Failed to create OCR manifest-failure note.", file=sys.stderr)
|
|
809
|
+
return posting_failure_exit(config, None, draft_note_ids)
|
|
810
|
+
if not finalize_posting(config, draft_note_ids):
|
|
811
|
+
return publish_failure_exit(config, draft_note_ids)
|
|
812
|
+
return 1 if strict_posting() else 0
|
|
813
|
+
|
|
814
|
+
|
|
740
815
|
def post_llm_provider_failure(
|
|
741
816
|
config: GitLabConfig,
|
|
742
817
|
warnings: Sequence[str],
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Validate and normalize versioned Open Code Review result outcomes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
OutcomeKind = Literal["clean", "warning", "partial", "failed", "skipped"]
|
|
10
|
+
|
|
11
|
+
MANIFEST_SCHEMA = "ocr.run-manifest/v1"
|
|
12
|
+
MAX_COVERAGE_RECORDS = 10_000
|
|
13
|
+
MAX_ITEM_ID_CHARS = 256
|
|
14
|
+
FAILURE_CLASSES = {
|
|
15
|
+
"provider",
|
|
16
|
+
"timeout",
|
|
17
|
+
"cancelled",
|
|
18
|
+
"configuration",
|
|
19
|
+
"input",
|
|
20
|
+
"budget",
|
|
21
|
+
"panic",
|
|
22
|
+
"unknown",
|
|
23
|
+
}
|
|
24
|
+
RUN_FAILURE_CLASSES = {
|
|
25
|
+
"input",
|
|
26
|
+
"configuration",
|
|
27
|
+
"timeout",
|
|
28
|
+
"cancelled",
|
|
29
|
+
"budget",
|
|
30
|
+
"internal",
|
|
31
|
+
"unknown",
|
|
32
|
+
}
|
|
33
|
+
LEGACY_OUTCOMES: dict[str, OutcomeKind] = {
|
|
34
|
+
"success": "clean",
|
|
35
|
+
"completed_with_warnings": "warning",
|
|
36
|
+
"completed_with_errors": "partial",
|
|
37
|
+
"budget_exceeded": "partial",
|
|
38
|
+
"skipped": "skipped",
|
|
39
|
+
}
|
|
40
|
+
MANIFEST_OUTCOMES: dict[str, OutcomeKind] = {
|
|
41
|
+
"complete": "clean",
|
|
42
|
+
"partial": "partial",
|
|
43
|
+
"failed": "failed",
|
|
44
|
+
"skipped": "skipped",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OcrResultContractError(ValueError):
|
|
49
|
+
"""An OCR result contradicts the supported legacy or manifest contract."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class ReviewOutcome:
|
|
54
|
+
"""Expose one validated OCR outcome without provider-specific branching."""
|
|
55
|
+
|
|
56
|
+
status: str
|
|
57
|
+
kind: OutcomeKind
|
|
58
|
+
budget_exceeded: bool
|
|
59
|
+
manifest_present: bool = False
|
|
60
|
+
selected_count: int = 0
|
|
61
|
+
completed_count: int = 0
|
|
62
|
+
reused_count: int = 0
|
|
63
|
+
failed_count: int = 0
|
|
64
|
+
waived_count: int = 0
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def requires_evidence_mcp(self) -> bool:
|
|
68
|
+
"""Return whether the result represents review work that must use evidence."""
|
|
69
|
+
|
|
70
|
+
return self.kind in {"clean", "warning", "partial"}
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def coverage_summary(self) -> str:
|
|
74
|
+
"""Return a path-free summary of versioned manifest coverage."""
|
|
75
|
+
|
|
76
|
+
if not self.manifest_present:
|
|
77
|
+
return ""
|
|
78
|
+
return (
|
|
79
|
+
f"Coverage: selected {self.selected_count}; completed {self.completed_count}; "
|
|
80
|
+
f"reused {self.reused_count}; failed {self.failed_count}; "
|
|
81
|
+
f"waived {self.waived_count}."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _budget_exceeded(result: Mapping[str, Any]) -> bool:
|
|
86
|
+
"""Read the optional summary budget flag without accepting truthy substitutes."""
|
|
87
|
+
|
|
88
|
+
summary = result.get("summary")
|
|
89
|
+
if summary is None:
|
|
90
|
+
return False
|
|
91
|
+
if not isinstance(summary, Mapping):
|
|
92
|
+
raise OcrResultContractError("field 'summary' must be an object")
|
|
93
|
+
value = summary.get("budget_exceeded", False)
|
|
94
|
+
if not isinstance(value, bool):
|
|
95
|
+
raise OcrResultContractError("field 'summary.budget_exceeded' must be a boolean")
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _coverage_ids(value: Any, field: str) -> tuple[set[str], list[Mapping[str, Any]]]:
|
|
100
|
+
"""Return bounded unique item identities from one coverage array."""
|
|
101
|
+
|
|
102
|
+
if not isinstance(value, list):
|
|
103
|
+
raise OcrResultContractError(f"field 'manifest.coverage.{field}' must be a list")
|
|
104
|
+
if len(value) > MAX_COVERAGE_RECORDS:
|
|
105
|
+
raise OcrResultContractError(
|
|
106
|
+
f"field 'manifest.coverage.{field}' exceeds {MAX_COVERAGE_RECORDS} records"
|
|
107
|
+
)
|
|
108
|
+
identities: set[str] = set()
|
|
109
|
+
records: list[Mapping[str, Any]] = []
|
|
110
|
+
for index, record in enumerate(value):
|
|
111
|
+
if not isinstance(record, Mapping):
|
|
112
|
+
raise OcrResultContractError(
|
|
113
|
+
f"field 'manifest.coverage.{field}[{index}]' must be an object"
|
|
114
|
+
)
|
|
115
|
+
item_id = record.get("item_id")
|
|
116
|
+
if (
|
|
117
|
+
not isinstance(item_id, str)
|
|
118
|
+
or not item_id
|
|
119
|
+
or len(item_id) > MAX_ITEM_ID_CHARS
|
|
120
|
+
or any(character.isspace() for character in item_id)
|
|
121
|
+
):
|
|
122
|
+
raise OcrResultContractError(
|
|
123
|
+
f"field 'manifest.coverage.{field}[{index}].item_id' is invalid"
|
|
124
|
+
)
|
|
125
|
+
if item_id in identities:
|
|
126
|
+
raise OcrResultContractError(
|
|
127
|
+
f"field 'manifest.coverage.{field}' contains duplicate item_id values"
|
|
128
|
+
)
|
|
129
|
+
identities.add(item_id)
|
|
130
|
+
records.append(record)
|
|
131
|
+
return identities, records
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _validate_failure_records(records: list[Mapping[str, Any]]) -> bool:
|
|
135
|
+
"""Validate item failure classes and report whether budget caused one."""
|
|
136
|
+
|
|
137
|
+
budget_failure = False
|
|
138
|
+
for index, record in enumerate(records):
|
|
139
|
+
classification = record.get("classification")
|
|
140
|
+
if classification not in FAILURE_CLASSES:
|
|
141
|
+
raise OcrResultContractError(
|
|
142
|
+
f"field 'manifest.coverage.failed[{index}].classification' is unsupported"
|
|
143
|
+
)
|
|
144
|
+
budget_failure = budget_failure or classification == "budget"
|
|
145
|
+
return budget_failure
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _validate_waived_records(records: list[Mapping[str, Any]]) -> None:
|
|
149
|
+
"""Require an explicit bounded reason for every waived item."""
|
|
150
|
+
|
|
151
|
+
for index, record in enumerate(records):
|
|
152
|
+
reason = record.get("reason")
|
|
153
|
+
if not isinstance(reason, str) or not reason.strip() or len(reason) > 2_000:
|
|
154
|
+
raise OcrResultContractError(
|
|
155
|
+
f"field 'manifest.coverage.waived[{index}].reason' is invalid"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _manifest_outcome(
|
|
160
|
+
status: str, manifest: Mapping[str, Any], budget_exceeded: bool
|
|
161
|
+
) -> ReviewOutcome:
|
|
162
|
+
"""Validate the v1 coverage partition and derive its terminal outcome."""
|
|
163
|
+
|
|
164
|
+
if manifest.get("schema_version") != MANIFEST_SCHEMA:
|
|
165
|
+
raise OcrResultContractError("field 'manifest.schema_version' is unsupported")
|
|
166
|
+
if manifest.get("operation") != "review":
|
|
167
|
+
raise OcrResultContractError("field 'manifest.operation' must be 'review'")
|
|
168
|
+
terminal_state = manifest.get("terminal_state")
|
|
169
|
+
if terminal_state not in MANIFEST_OUTCOMES:
|
|
170
|
+
raise OcrResultContractError("field 'manifest.terminal_state' is unsupported")
|
|
171
|
+
if status != terminal_state:
|
|
172
|
+
raise OcrResultContractError("fields 'status' and 'manifest.terminal_state' disagree")
|
|
173
|
+
|
|
174
|
+
coverage = manifest.get("coverage")
|
|
175
|
+
if not isinstance(coverage, Mapping):
|
|
176
|
+
raise OcrResultContractError("field 'manifest.coverage' must be an object")
|
|
177
|
+
|
|
178
|
+
selected, _selected_records = _coverage_ids(coverage.get("selected"), "selected")
|
|
179
|
+
completed, _completed_records = _coverage_ids(coverage.get("completed"), "completed")
|
|
180
|
+
reused, _reused_records = _coverage_ids(coverage.get("reused"), "reused")
|
|
181
|
+
failed, failed_records = _coverage_ids(coverage.get("failed"), "failed")
|
|
182
|
+
waived, waived_records = _coverage_ids(coverage.get("waived"), "waived")
|
|
183
|
+
|
|
184
|
+
terminal_sets = (completed, reused, failed, waived)
|
|
185
|
+
terminal_union: set[str] = set()
|
|
186
|
+
for terminal_set in terminal_sets:
|
|
187
|
+
if terminal_union.intersection(terminal_set):
|
|
188
|
+
raise OcrResultContractError("manifest coverage terminal sets are not disjoint")
|
|
189
|
+
terminal_union.update(terminal_set)
|
|
190
|
+
if terminal_union != selected:
|
|
191
|
+
raise OcrResultContractError(
|
|
192
|
+
"manifest coverage selected items do not equal the terminal partition"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
budget_failure = _validate_failure_records(failed_records)
|
|
196
|
+
_validate_waived_records(waived_records)
|
|
197
|
+
|
|
198
|
+
run_failure = manifest.get("run_failure")
|
|
199
|
+
if run_failure is not None:
|
|
200
|
+
if not isinstance(run_failure, Mapping):
|
|
201
|
+
raise OcrResultContractError("field 'manifest.run_failure' must be an object")
|
|
202
|
+
if run_failure.get("classification") not in RUN_FAILURE_CLASSES:
|
|
203
|
+
raise OcrResultContractError(
|
|
204
|
+
"field 'manifest.run_failure.classification' is unsupported"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
derived_state = (
|
|
208
|
+
"failed"
|
|
209
|
+
if run_failure is not None
|
|
210
|
+
else "skipped"
|
|
211
|
+
if not selected
|
|
212
|
+
else "complete"
|
|
213
|
+
if not failed
|
|
214
|
+
else "failed"
|
|
215
|
+
if failed == selected
|
|
216
|
+
else "partial"
|
|
217
|
+
)
|
|
218
|
+
if terminal_state != derived_state:
|
|
219
|
+
raise OcrResultContractError(
|
|
220
|
+
"field 'manifest.terminal_state' disagrees with coverage and run_failure"
|
|
221
|
+
)
|
|
222
|
+
if budget_exceeded and not (
|
|
223
|
+
budget_failure
|
|
224
|
+
or (isinstance(run_failure, Mapping) and run_failure.get("classification") == "budget")
|
|
225
|
+
):
|
|
226
|
+
raise OcrResultContractError(
|
|
227
|
+
"field 'summary.budget_exceeded' has no matching manifest budget failure"
|
|
228
|
+
)
|
|
229
|
+
if terminal_state in {"complete", "skipped"} and budget_exceeded:
|
|
230
|
+
raise OcrResultContractError(
|
|
231
|
+
"complete or skipped manifest cannot report summary.budget_exceeded"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return ReviewOutcome(
|
|
235
|
+
status=status,
|
|
236
|
+
kind=MANIFEST_OUTCOMES[status],
|
|
237
|
+
budget_exceeded=budget_exceeded,
|
|
238
|
+
manifest_present=True,
|
|
239
|
+
selected_count=len(selected),
|
|
240
|
+
completed_count=len(completed),
|
|
241
|
+
reused_count=len(reused),
|
|
242
|
+
failed_count=len(failed),
|
|
243
|
+
waived_count=len(waived),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def parse_result_outcome(result: Mapping[str, Any]) -> ReviewOutcome:
|
|
248
|
+
"""Validate one OCR result and normalize legacy and manifest outcomes."""
|
|
249
|
+
|
|
250
|
+
status_value = result.get("status", "success")
|
|
251
|
+
if not isinstance(status_value, str) or not status_value:
|
|
252
|
+
raise OcrResultContractError("field 'status' must be a non-empty string")
|
|
253
|
+
budget_exceeded = _budget_exceeded(result)
|
|
254
|
+
manifest = result.get("manifest")
|
|
255
|
+
|
|
256
|
+
if manifest is not None:
|
|
257
|
+
if not isinstance(manifest, Mapping):
|
|
258
|
+
raise OcrResultContractError("field 'manifest' must be an object")
|
|
259
|
+
return _manifest_outcome(status_value, manifest, budget_exceeded)
|
|
260
|
+
|
|
261
|
+
if status_value not in LEGACY_OUTCOMES:
|
|
262
|
+
if status_value in MANIFEST_OUTCOMES:
|
|
263
|
+
raise OcrResultContractError(f"status {status_value!r} requires a supported manifest")
|
|
264
|
+
raise OcrResultContractError("field 'status' is unsupported")
|
|
265
|
+
if (status_value == "budget_exceeded") != budget_exceeded:
|
|
266
|
+
raise OcrResultContractError("fields 'status' and 'summary.budget_exceeded' disagree")
|
|
267
|
+
return ReviewOutcome(
|
|
268
|
+
status=status_value,
|
|
269
|
+
kind=LEGACY_OUTCOMES[status_value],
|
|
270
|
+
budget_exceeded=budget_exceeded,
|
|
271
|
+
)
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/review_runner.py
RENAMED
|
@@ -32,6 +32,7 @@ from ocr_toolkit.ocr_result import (
|
|
|
32
32
|
attach_toolkit_metadata,
|
|
33
33
|
)
|
|
34
34
|
from ocr_toolkit.providers.gitlab import invocation_identifiers
|
|
35
|
+
from ocr_toolkit.result_contract import OcrResultContractError, parse_result_outcome
|
|
35
36
|
|
|
36
37
|
STDERR_PROBE_BYTES = 64 * 1024
|
|
37
38
|
DEFAULT_DIAGNOSTIC_CHARS = 4_000
|
|
@@ -83,28 +84,29 @@ def _mcp_usage_receipt(
|
|
|
83
84
|
) -> dict[str, object]:
|
|
84
85
|
"""Return bounded MCP usage tied to one validated review-time registry."""
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
try:
|
|
88
|
+
outcome = parse_result_outcome(payload)
|
|
89
|
+
except OcrResultContractError as exc:
|
|
90
|
+
raise ReviewRunnerError(f"OCR result has an unsupported outcome contract: {exc}") from exc
|
|
91
|
+
|
|
92
|
+
if outcome.kind == "skipped":
|
|
93
|
+
tool_calls = payload.get("tool_calls")
|
|
94
|
+
by_tool = tool_calls.get("by_tool") if isinstance(tool_calls, dict) else None
|
|
95
|
+
legacy_message_invalid = (
|
|
96
|
+
not outcome.manifest_present and payload.get("message") != "No supported files changed."
|
|
97
|
+
)
|
|
98
|
+
if (
|
|
99
|
+
legacy_message_invalid
|
|
100
|
+
or payload.get("comments") != []
|
|
101
|
+
or not isinstance(tool_calls, dict)
|
|
102
|
+
or tool_calls.get("total") != 0
|
|
103
|
+
or by_tool != {}
|
|
104
|
+
):
|
|
105
|
+
raise ReviewRunnerError(
|
|
106
|
+
"OCR skipped result does not match the pinned no-supported-files contract"
|
|
107
|
+
)
|
|
108
|
+
return {"mcp_usage": {}}
|
|
109
|
+
|
|
108
110
|
tool_calls = payload.get("tool_calls")
|
|
109
111
|
by_tool = tool_calls.get("by_tool") if isinstance(tool_calls, dict) else None
|
|
110
112
|
owners = {
|
|
@@ -124,7 +126,7 @@ def _mcp_usage_receipt(
|
|
|
124
126
|
):
|
|
125
127
|
owner = owners[tool]
|
|
126
128
|
usage[owner] = usage.get(owner, 0) + count
|
|
127
|
-
if usage.get(mcp_config.BUILTIN_EVIDENCE_SERVER, 0) <= 0:
|
|
129
|
+
if outcome.requires_evidence_mcp and usage.get(mcp_config.BUILTIN_EVIDENCE_SERVER, 0) <= 0:
|
|
128
130
|
raise ReviewRunnerError(f"OCR review did not call the mandatory {TOOL_NAME} tool")
|
|
129
131
|
return {"mcp_usage": dict(sorted(usage.items()))}
|
|
130
132
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/__init__.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/git.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/language.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/common/markdown.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/config_writer.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/configure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/mcp.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/evidence/store.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/mcp_config.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/ocr_result.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/gitlab.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/markers.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.4.2 → open_code_review_toolkit-0.4.3}/src/ocr_toolkit/posting/result.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|