open-code-review-toolkit 0.6.0__tar.gz → 0.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/PKG-INFO +5 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/README.md +3 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/pyproject.toml +1 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/cli.py +6 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/artifacts.py +24 -3
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collect.py +35 -3
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/orchestration.py +37 -22
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/contracts.py +1 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/templates.py +4 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/mcp.py +86 -31
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/model.py +5 -2
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/schema.py +1 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/project.py +69 -45
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/repository.py +47 -1
- open_code_review_toolkit-0.6.2/src/ocr_toolkit/evidence/review_context.py +348 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/contracts.py +3 -2
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/core.py +62 -14
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/readback.py +25 -5
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/mcp_config.py +4 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/ocr_result.py +7 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/approval.py +50 -1
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/formatting.py +2 -2
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/workflow.py +1 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/preflight.py +1 -1
- open_code_review_toolkit-0.6.2/src/ocr_toolkit/providers/gitlab.py +274 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/review_runner.py +134 -10
- open_code_review_toolkit-0.6.0/src/ocr_toolkit/providers/gitlab.py +0 -38
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/.gitignore +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/LICENSE +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/result_contract.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/xeonvs/open-code-review-toolkit/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/xeonvs/open-code-review-toolkit/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Code of Conduct, https://github.com/xeonvs/open-code-review-toolkit/blob/main/CODE_OF_CONDUCT.md
|
|
9
10
|
Author: Open Code Review Toolkit contributors
|
|
10
11
|
License:
|
|
11
12
|
Apache License
|
|
@@ -227,6 +228,8 @@ Description-Content-Type: text/markdown
|
|
|
227
228
|
# Open Code Review Toolkit
|
|
228
229
|
|
|
229
230
|
[](https://www.bestpractices.dev/projects/13906)
|
|
231
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/xeonvs/open-code-review-toolkit)
|
|
232
|
+
[](https://github.com/xeonvs/open-code-review-toolkit/actions/workflows/codeql.yml)
|
|
230
233
|
|
|
231
234
|
Open Code Review Toolkit is an unofficial GitLab CI integration layer for [Alibaba Open Code Review](https://github.com/alibaba/open-code-review). It provides bounded repository evidence, a compact review bootstrap, a built-in read-only MCP server, environment-driven OCR configuration, preflight validation, and safe GitLab merge-request posting. It does **not** bundle or download the `ocr` binary.
|
|
232
235
|
|
|
@@ -297,6 +300,7 @@ Repository content, OCR output, and provider responses are untrusted inputs. The
|
|
|
297
300
|
## Development and release
|
|
298
301
|
|
|
299
302
|
- [Contributing](CONTRIBUTING.md)
|
|
303
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
300
304
|
- [Development guide](docs/development.md)
|
|
301
305
|
- [Security policy](SECURITY.md)
|
|
302
306
|
- [Release process](docs/release.md)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Open Code Review Toolkit
|
|
2
2
|
|
|
3
3
|
[](https://www.bestpractices.dev/projects/13906)
|
|
4
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/xeonvs/open-code-review-toolkit)
|
|
5
|
+
[](https://github.com/xeonvs/open-code-review-toolkit/actions/workflows/codeql.yml)
|
|
4
6
|
|
|
5
7
|
Open Code Review Toolkit is an unofficial GitLab CI integration layer for [Alibaba Open Code Review](https://github.com/alibaba/open-code-review). It provides bounded repository evidence, a compact review bootstrap, a built-in read-only MCP server, environment-driven OCR configuration, preflight validation, and safe GitLab merge-request posting. It does **not** bundle or download the `ocr` binary.
|
|
6
8
|
|
|
@@ -71,6 +73,7 @@ Repository content, OCR output, and provider responses are untrusted inputs. The
|
|
|
71
73
|
## Development and release
|
|
72
74
|
|
|
73
75
|
- [Contributing](CONTRIBUTING.md)
|
|
76
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
74
77
|
- [Development guide](docs/development.md)
|
|
75
78
|
- [Security policy](SECURITY.md)
|
|
76
79
|
- [Release process](docs/release.md)
|
|
@@ -30,6 +30,7 @@ Homepage = "https://github.com/xeonvs/open-code-review-toolkit"
|
|
|
30
30
|
Repository = "https://github.com/xeonvs/open-code-review-toolkit"
|
|
31
31
|
Issues = "https://github.com/xeonvs/open-code-review-toolkit/issues"
|
|
32
32
|
Changelog = "https://github.com/xeonvs/open-code-review-toolkit/blob/main/CHANGELOG.md"
|
|
33
|
+
"Code of Conduct" = "https://github.com/xeonvs/open-code-review-toolkit/blob/main/CODE_OF_CONDUCT.md"
|
|
33
34
|
|
|
34
35
|
[project.scripts]
|
|
35
36
|
ocr-ci = "ocr_toolkit.cli:main"
|
{open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/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.6.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 6,
|
|
21
|
+
__version__ = version = '0.6.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 6, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from ocr_toolkit import configure, mcp_config, preflight, review_runner
|
|
10
|
+
from ocr_toolkit import __version__, configure, mcp_config, preflight, review_runner
|
|
11
11
|
from ocr_toolkit.posting.workflow import main as posting_main
|
|
12
12
|
|
|
13
13
|
|
|
@@ -18,6 +18,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
18
18
|
prog="ocr-ci",
|
|
19
19
|
description="Safe CI integration helpers for Open Code Review.",
|
|
20
20
|
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"--version",
|
|
23
|
+
action="version",
|
|
24
|
+
version=f"%(prog)s {__version__}",
|
|
25
|
+
)
|
|
21
26
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
22
27
|
subparsers.add_parser("preflight", help="Validate OCR, GitLab, and LLM access.")
|
|
23
28
|
subparsers.add_parser("configure", help="Write the OCR runtime configuration.")
|
|
@@ -15,6 +15,7 @@ class EvidenceArtifacts:
|
|
|
15
15
|
directory: Path
|
|
16
16
|
store: Path
|
|
17
17
|
bootstrap: Path
|
|
18
|
+
policy_rules: Path
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def repository_artifacts(root: Path | None = None) -> EvidenceArtifacts:
|
|
@@ -26,6 +27,7 @@ def repository_artifacts(root: Path | None = None) -> EvidenceArtifacts:
|
|
|
26
27
|
directory=directory,
|
|
27
28
|
store=directory / "evidence.json",
|
|
28
29
|
bootstrap=directory / "bootstrap.md",
|
|
30
|
+
policy_rules=directory / "policy-rules.json",
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
|
|
@@ -42,8 +44,8 @@ def prepare_artifact_directory(artifacts: EvidenceArtifacts) -> None:
|
|
|
42
44
|
directory.chmod(0o700)
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def
|
|
46
|
-
"""Write one
|
|
47
|
+
def write_private_bytes(path: Path, content: bytes) -> None:
|
|
48
|
+
"""Write one private byte artifact without following the final symlink."""
|
|
47
49
|
|
|
48
50
|
if path.parent.is_symlink() or not path.parent.is_dir():
|
|
49
51
|
raise OSError(f"private artifact parent is not a safe directory: {path.parent}")
|
|
@@ -63,7 +65,7 @@ def write_private_text(path: Path, content: str) -> None:
|
|
|
63
65
|
raise OSError(f"private artifact is an existing hard link: {path}")
|
|
64
66
|
os.fchmod(descriptor, 0o600)
|
|
65
67
|
os.ftruncate(descriptor, 0)
|
|
66
|
-
stream = os.fdopen(descriptor, "
|
|
68
|
+
stream = os.fdopen(descriptor, "wb")
|
|
67
69
|
descriptor = -1
|
|
68
70
|
with stream:
|
|
69
71
|
stream.write(content)
|
|
@@ -72,3 +74,22 @@ def write_private_text(path: Path, content: str) -> None:
|
|
|
72
74
|
if descriptor >= 0:
|
|
73
75
|
os.close(descriptor)
|
|
74
76
|
raise
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def write_private_text(path: Path, content: str) -> None:
|
|
80
|
+
"""Write one internal UTF-8 artifact through the private byte boundary."""
|
|
81
|
+
|
|
82
|
+
write_private_bytes(path, content.encode("utf-8"))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def remove_private_artifact(path: Path) -> None:
|
|
86
|
+
"""Remove one toolkit-owned artifact name without following its target."""
|
|
87
|
+
|
|
88
|
+
if path.parent.is_symlink() or not path.parent.is_dir():
|
|
89
|
+
raise OSError(f"private artifact parent is not a safe directory: {path.parent}")
|
|
90
|
+
try:
|
|
91
|
+
path.unlink()
|
|
92
|
+
except FileNotFoundError:
|
|
93
|
+
return
|
|
94
|
+
except IsADirectoryError as exc:
|
|
95
|
+
raise OSError(f"private artifact path is not a file: {path}") from exc
|
|
@@ -16,6 +16,7 @@ from ocr_toolkit.evidence.repository import (
|
|
|
16
16
|
file_deltas,
|
|
17
17
|
)
|
|
18
18
|
from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError
|
|
19
|
+
from ocr_toolkit.evidence.store.contracts import POLICY_KINDS
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _commit_refs(
|
|
@@ -44,13 +45,18 @@ def _component_for_path(path: str) -> str:
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def collect_repository_evidence(
|
|
47
|
-
root: Path | None = None,
|
|
48
|
+
root: Path | None = None,
|
|
49
|
+
*,
|
|
50
|
+
base_ref: str | None = None,
|
|
51
|
+
head_ref: str | None = None,
|
|
52
|
+
policy_ref: str | None = None,
|
|
48
53
|
) -> EvidenceStore:
|
|
49
54
|
"""Build one evidence store from immutable refs and existing bounded collectors."""
|
|
50
55
|
|
|
51
56
|
reader = GitRepositoryReader(root or Path.cwd())
|
|
52
57
|
base_sha, head_sha = _commit_refs(reader, base_ref, head_ref)
|
|
53
58
|
changed = reader.changed_paths(base_sha, head_sha)
|
|
59
|
+
policy_sha = reader.resolve_commit(policy_ref) if policy_ref is not None else base_sha
|
|
54
60
|
base = build_file_snapshot(reader, base_sha, RefRole.BASE, paths=changed)
|
|
55
61
|
head = build_file_snapshot(reader, head_sha, RefRole.HEAD, paths=changed)
|
|
56
62
|
base_paths = {record.source_path for record in base.records}
|
|
@@ -64,6 +70,19 @@ def collect_repository_evidence(
|
|
|
64
70
|
changed_paths=changed,
|
|
65
71
|
coverage_sink=base_coverage,
|
|
66
72
|
)
|
|
73
|
+
policy_facts, policy_diagnostics = collect_ref_facts(
|
|
74
|
+
reader,
|
|
75
|
+
policy_sha,
|
|
76
|
+
RefRole.POLICY,
|
|
77
|
+
changed_paths=changed,
|
|
78
|
+
)
|
|
79
|
+
policy_facts = [record for record in policy_facts if record.kind in POLICY_KINDS]
|
|
80
|
+
policy = EvidenceSnapshot(
|
|
81
|
+
RefRole.POLICY,
|
|
82
|
+
policy_sha,
|
|
83
|
+
tuple(policy_facts),
|
|
84
|
+
diagnostics=tuple(policy_diagnostics),
|
|
85
|
+
)
|
|
67
86
|
head_facts, head_fact_diagnostics = collect_ref_facts(
|
|
68
87
|
reader,
|
|
69
88
|
head_sha,
|
|
@@ -87,10 +106,12 @@ def collect_repository_evidence(
|
|
|
87
106
|
)
|
|
88
107
|
all_coverage = tuple((*base.coverage, *head.coverage))
|
|
89
108
|
snapshot_deltas = file_deltas(base, head)
|
|
90
|
-
store = EvidenceStore(base=base, head=head)
|
|
109
|
+
store = EvidenceStore(base=base, head=head, policy=policy)
|
|
91
110
|
typed_facts = [*base_facts, *head_facts]
|
|
92
111
|
rejected_snapshot_records = [
|
|
93
|
-
record
|
|
112
|
+
record
|
|
113
|
+
for record in (*base.records, *head.records, *policy.records)
|
|
114
|
+
if not store.add(record)
|
|
94
115
|
]
|
|
95
116
|
if rejected_snapshot_records:
|
|
96
117
|
# Snapshots and their record-id indexes are one atomic contract. Persisting a
|
|
@@ -98,6 +119,16 @@ def collect_repository_evidence(
|
|
|
98
119
|
raise EvidenceStoreError(
|
|
99
120
|
"repository snapshot records exceed the configured evidence store limits"
|
|
100
121
|
)
|
|
122
|
+
store.policy = EvidenceSnapshot(
|
|
123
|
+
RefRole.POLICY,
|
|
124
|
+
policy_sha,
|
|
125
|
+
tuple(
|
|
126
|
+
record
|
|
127
|
+
for record in store.records
|
|
128
|
+
if record.ref is RefRole.POLICY and record.commit_sha == policy_sha
|
|
129
|
+
),
|
|
130
|
+
diagnostics=policy.diagnostics,
|
|
131
|
+
)
|
|
101
132
|
for coverage in all_coverage:
|
|
102
133
|
if not store.add_coverage(coverage):
|
|
103
134
|
raise EvidenceStoreError(
|
|
@@ -174,6 +205,7 @@ def collect_repository_evidence(
|
|
|
174
205
|
*head.diagnostics,
|
|
175
206
|
*base_fact_diagnostics,
|
|
176
207
|
*head_fact_diagnostics,
|
|
208
|
+
*policy_diagnostics,
|
|
177
209
|
):
|
|
178
210
|
store.add_diagnostic(diagnostic)
|
|
179
211
|
return store
|
|
@@ -76,7 +76,11 @@ def collect_ref_facts(
|
|
|
76
76
|
|
|
77
77
|
records = []
|
|
78
78
|
diagnostics = []
|
|
79
|
-
trust =
|
|
79
|
+
trust = (
|
|
80
|
+
TrustClass.TARGET_REPOSITORY
|
|
81
|
+
if ref in {RefRole.BASE, RefRole.POLICY}
|
|
82
|
+
else TrustClass.SOURCE_REPOSITORY
|
|
83
|
+
)
|
|
80
84
|
changed_exact = tuple(sorted(set(changed_paths)))
|
|
81
85
|
changed = {path.casefold() for path in changed_exact}
|
|
82
86
|
entries = reader.list_objects(commit_sha)
|
|
@@ -118,7 +122,7 @@ def collect_ref_facts(
|
|
|
118
122
|
changed_exact,
|
|
119
123
|
)
|
|
120
124
|
)
|
|
121
|
-
if ref is RefRole.
|
|
125
|
+
if ref is RefRole.POLICY
|
|
122
126
|
else set()
|
|
123
127
|
)
|
|
124
128
|
applicable_guidance = tuple(
|
|
@@ -158,7 +162,7 @@ def collect_ref_facts(
|
|
|
158
162
|
(
|
|
159
163
|
entry
|
|
160
164
|
for entry in entries
|
|
161
|
-
if ref is RefRole.
|
|
165
|
+
if ref is RefRole.POLICY and entry.path == ACCEPTED_DECISIONS_PATH
|
|
162
166
|
),
|
|
163
167
|
None,
|
|
164
168
|
)
|
|
@@ -188,7 +192,8 @@ def collect_ref_facts(
|
|
|
188
192
|
candidates = tuple(
|
|
189
193
|
entry
|
|
190
194
|
for entry in entries
|
|
191
|
-
if
|
|
195
|
+
if ref is not RefRole.POLICY
|
|
196
|
+
and (
|
|
192
197
|
is_supported_manifest(entry.path)
|
|
193
198
|
or PurePosixPath(entry.path).name.casefold().startswith(".gitlab-ci")
|
|
194
199
|
or is_context_yaml(entry.path, changed)
|
|
@@ -380,7 +385,7 @@ def collect_ref_facts(
|
|
|
380
385
|
]
|
|
381
386
|
elif path == ACCEPTED_DECISIONS_PATH:
|
|
382
387
|
facts = []
|
|
383
|
-
if ref == RefRole.
|
|
388
|
+
if ref == RefRole.POLICY:
|
|
384
389
|
parsed_decisions = parse_accepted_decisions(text, changed_paths=changed_exact)
|
|
385
390
|
diagnostics.extend(
|
|
386
391
|
f"{ref.value}:{path}: {notice}" for notice in parsed_decisions.diagnostics
|
|
@@ -396,7 +401,7 @@ def collect_ref_facts(
|
|
|
396
401
|
]
|
|
397
402
|
elif guidance_source:
|
|
398
403
|
facts = []
|
|
399
|
-
if ref == RefRole.
|
|
404
|
+
if ref == RefRole.POLICY and path in policy_paths:
|
|
400
405
|
document = guidance_document(path, text, changed_exact)
|
|
401
406
|
facts = [
|
|
402
407
|
ManifestFact(
|
|
@@ -490,25 +495,35 @@ def collect_ref_facts(
|
|
|
490
495
|
trust=trust,
|
|
491
496
|
)
|
|
492
497
|
)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
template_facts, template_observations, template_notices = collect_template_files(plugin_context)
|
|
502
|
-
records.extend(
|
|
503
|
-
plugin_records(
|
|
504
|
-
(*plugin_facts, *template_facts),
|
|
498
|
+
plugin_observations = ()
|
|
499
|
+
template_observations = ()
|
|
500
|
+
if ref is not RefRole.POLICY:
|
|
501
|
+
plugin_context = FrameworkPluginContext(
|
|
502
|
+
records=tuple(records),
|
|
503
|
+
entries=entries,
|
|
504
|
+
source_statuses=tuple(sorted(source_statuses.values(), key=lambda item: item.path)),
|
|
505
|
+
changed_paths=changed_exact,
|
|
505
506
|
ref=ref,
|
|
506
507
|
commit_sha=commit_sha,
|
|
507
|
-
trust=trust,
|
|
508
508
|
)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
509
|
+
plugin_facts, plugin_observations, plugin_notices = collect_framework_plugins(
|
|
510
|
+
plugin_context
|
|
511
|
+
)
|
|
512
|
+
template_facts, template_observations, template_notices = collect_template_files(
|
|
513
|
+
plugin_context
|
|
514
|
+
)
|
|
515
|
+
records.extend(
|
|
516
|
+
plugin_records(
|
|
517
|
+
(*plugin_facts, *template_facts),
|
|
518
|
+
ref=ref,
|
|
519
|
+
commit_sha=commit_sha,
|
|
520
|
+
trust=trust,
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
diagnostics.extend(
|
|
524
|
+
f"{ref.value}:{notice}" for notice in (*plugin_notices, *template_notices)
|
|
525
|
+
)
|
|
526
|
+
if coverage_sink is not None and ref is not RefRole.POLICY:
|
|
512
527
|
for (domain, scope), observations in sorted(coverage_observations.items()):
|
|
513
528
|
coverage_sink.append(
|
|
514
529
|
compose_coverage(
|
|
@@ -93,7 +93,10 @@ def collect_template_files(
|
|
|
93
93
|
}
|
|
94
94
|
limited_components: set[tuple[str, str]] = set()
|
|
95
95
|
truncated = False
|
|
96
|
-
|
|
96
|
+
changed_paths = frozenset(context.changed_paths)
|
|
97
|
+
for entry in sorted(
|
|
98
|
+
context.entries, key=lambda item: (item.path not in changed_paths, item.path)
|
|
99
|
+
):
|
|
97
100
|
description = _template_description(entry.path, context)
|
|
98
101
|
if description is None:
|
|
99
102
|
continue
|
{open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/mcp.py
RENAMED
|
@@ -93,6 +93,16 @@ def evidence_summary(store: EvidenceStore) -> dict[str, object]:
|
|
|
93
93
|
for record in store.records
|
|
94
94
|
if record.kind in {"repository.accepted_decision", "repository.guidance"}
|
|
95
95
|
)
|
|
96
|
+
mr_context_records = tuple(
|
|
97
|
+
record for record in store.records if record.kind == "review.merge_request_context"
|
|
98
|
+
)
|
|
99
|
+
merge_request_context = {
|
|
100
|
+
"contract": ("review.merge-request-context/v1" if mr_context_records else "absent"),
|
|
101
|
+
"records": len(mr_context_records),
|
|
102
|
+
"trust": "invocation" if mr_context_records else None,
|
|
103
|
+
"content_role": "untrusted_data" if mr_context_records else None,
|
|
104
|
+
"authoritative_for_actions": False,
|
|
105
|
+
}
|
|
96
106
|
policy = {
|
|
97
107
|
"accepted_decisions": sum(
|
|
98
108
|
record.kind == "repository.accepted_decision" for record in policy_records
|
|
@@ -102,7 +112,7 @@ def evidence_summary(store: EvidenceStore) -> dict[str, object]:
|
|
|
102
112
|
),
|
|
103
113
|
"structured_target_records": sum(
|
|
104
114
|
not is_legacy_policy_value(record.value)
|
|
105
|
-
and record.ref.value
|
|
115
|
+
and record.ref.value in {"base", "policy"}
|
|
106
116
|
and record.trust.value == "target_repository"
|
|
107
117
|
for record in policy_records
|
|
108
118
|
),
|
|
@@ -110,14 +120,16 @@ def evidence_summary(store: EvidenceStore) -> dict[str, object]:
|
|
|
110
120
|
is_legacy_policy_value(record.value) for record in policy_records
|
|
111
121
|
),
|
|
112
122
|
"target_only": all(
|
|
113
|
-
record.ref.value
|
|
123
|
+
record.ref.value in {"base", "policy"} and record.trust.value == "target_repository"
|
|
114
124
|
for record in policy_records
|
|
115
125
|
),
|
|
116
126
|
"authoritative_for_actions": False,
|
|
117
127
|
}
|
|
118
128
|
return {
|
|
119
|
-
"schema_version":
|
|
129
|
+
"schema_version": store.schema_version,
|
|
120
130
|
"policy": policy,
|
|
131
|
+
"merge_request_context": merge_request_context,
|
|
132
|
+
"policy_ref": store.policy.commit_sha if store.policy else None,
|
|
121
133
|
"coverage_contract": ("repository.evidence-coverage/v1" if store.coverage else "absent"),
|
|
122
134
|
"base": store.base.commit_sha if store.base else None,
|
|
123
135
|
"head": store.head.commit_sha if store.head else None,
|
|
@@ -136,9 +148,9 @@ def _optional_filter(arguments: dict[str, object], name: str) -> str | None:
|
|
|
136
148
|
"""Read one bounded optional exact-match filter."""
|
|
137
149
|
|
|
138
150
|
value = arguments.get(name)
|
|
139
|
-
if value is None:
|
|
151
|
+
if value is None or value == "":
|
|
140
152
|
return None
|
|
141
|
-
if not isinstance(value, str) or
|
|
153
|
+
if not isinstance(value, str) or len(value) > 256:
|
|
142
154
|
raise EvidenceMCPError(f"{name} must be a non-empty string of at most 256 characters")
|
|
143
155
|
return value
|
|
144
156
|
|
|
@@ -154,9 +166,9 @@ def _encode_cursor(offset: int, query: _Query) -> str:
|
|
|
154
166
|
def _decode_cursor(value: object, query: _Query) -> int:
|
|
155
167
|
"""Validate and decode a cursor bound to the current filters."""
|
|
156
168
|
|
|
157
|
-
if value is None:
|
|
169
|
+
if value is None or value == "":
|
|
158
170
|
return 0
|
|
159
|
-
if not isinstance(value, str) or
|
|
171
|
+
if not isinstance(value, str) or len(value) > 256:
|
|
160
172
|
raise EvidenceMCPError("cursor must be a bounded opaque string")
|
|
161
173
|
try:
|
|
162
174
|
padded = value + "=" * (-len(value) % 4)
|
|
@@ -185,8 +197,8 @@ def _list_records(store: EvidenceStore, arguments: dict[str, object]) -> dict[st
|
|
|
185
197
|
raise EvidenceMCPError("delta_kind requires kind=repository.evidence_delta")
|
|
186
198
|
if query.kind == "repository.evidence_delta" and query.ref is not None:
|
|
187
199
|
raise EvidenceMCPError("evidence deltas span base and head and do not accept ref")
|
|
188
|
-
if query.ref not in {None, "base", "head", "shared"}:
|
|
189
|
-
raise EvidenceMCPError("ref must be base, head, or shared")
|
|
200
|
+
if query.ref not in {None, "base", "head", "policy", "shared"}:
|
|
201
|
+
raise EvidenceMCPError("ref must be base, head, policy, or shared")
|
|
190
202
|
page_size = arguments.get("page_size", DEFAULT_PAGE_SIZE)
|
|
191
203
|
if isinstance(page_size, bool) or not isinstance(page_size, int):
|
|
192
204
|
raise EvidenceMCPError("page_size must be an integer")
|
|
@@ -254,29 +266,33 @@ def call_tool(store: EvidenceStore, arguments: object) -> dict[str, object]:
|
|
|
254
266
|
if not isinstance(arguments, dict):
|
|
255
267
|
raise EvidenceMCPError("tool arguments must be an object")
|
|
256
268
|
typed = cast(dict[str, object], arguments)
|
|
269
|
+
# The public schema is one union-shaped object. OCR/provider adapters may
|
|
270
|
+
# materialize every declared property even when an action does not consume
|
|
271
|
+
# it, so reject unknown names globally and let each action read only its own
|
|
272
|
+
# fields.
|
|
273
|
+
declared = {
|
|
274
|
+
"action",
|
|
275
|
+
"kind",
|
|
276
|
+
"delta_kind",
|
|
277
|
+
"component",
|
|
278
|
+
"ref",
|
|
279
|
+
"page_size",
|
|
280
|
+
"cursor",
|
|
281
|
+
"id",
|
|
282
|
+
}
|
|
283
|
+
unknown = set(typed) - declared
|
|
284
|
+
if unknown:
|
|
285
|
+
raise EvidenceMCPError(f"unsupported tool argument: {sorted(unknown)[0]}")
|
|
286
|
+
|
|
257
287
|
action = typed.get("action")
|
|
258
288
|
if action == "summary":
|
|
259
|
-
allowed = {"action"}
|
|
260
289
|
payload = evidence_summary(store)
|
|
261
290
|
elif action == "list":
|
|
262
|
-
allowed = {
|
|
263
|
-
"action",
|
|
264
|
-
"kind",
|
|
265
|
-
"delta_kind",
|
|
266
|
-
"component",
|
|
267
|
-
"ref",
|
|
268
|
-
"page_size",
|
|
269
|
-
"cursor",
|
|
270
|
-
}
|
|
271
291
|
payload = _list_records(store, typed)
|
|
272
292
|
elif action == "get":
|
|
273
|
-
allowed = {"action", "id"}
|
|
274
293
|
payload = _get_record(store, typed)
|
|
275
294
|
else:
|
|
276
295
|
raise EvidenceMCPError("action must be summary, list, or get")
|
|
277
|
-
unknown = set(typed) - allowed
|
|
278
|
-
if unknown:
|
|
279
|
-
raise EvidenceMCPError(f"unsupported tool argument: {sorted(unknown)[0]}")
|
|
280
296
|
return _text_result(payload)
|
|
281
297
|
|
|
282
298
|
|
|
@@ -299,14 +315,53 @@ def _tool_definition() -> dict[str, object]:
|
|
|
299
315
|
"additionalProperties": False,
|
|
300
316
|
"required": ["action"],
|
|
301
317
|
"properties": {
|
|
302
|
-
"action": {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
318
|
+
"action": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"enum": ["summary", "list", "get"],
|
|
321
|
+
"description": (
|
|
322
|
+
"Use summary for counts, list to filter records, and get only after "
|
|
323
|
+
"list returns a stable record id."
|
|
324
|
+
),
|
|
325
|
+
},
|
|
326
|
+
"kind": {
|
|
327
|
+
"type": "string",
|
|
328
|
+
"maxLength": 256,
|
|
329
|
+
"description": "Optional exact record-kind filter for action=list only.",
|
|
330
|
+
},
|
|
331
|
+
"delta_kind": {
|
|
332
|
+
"type": "string",
|
|
333
|
+
"maxLength": 256,
|
|
334
|
+
"description": (
|
|
335
|
+
"Optional original fact-kind filter for action=list with "
|
|
336
|
+
"kind=repository.evidence_delta only."
|
|
337
|
+
),
|
|
338
|
+
},
|
|
339
|
+
"component": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"maxLength": 256,
|
|
342
|
+
"description": "Optional exact component filter for action=list only.",
|
|
343
|
+
},
|
|
344
|
+
"ref": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"enum": ["base", "head", "policy", "shared"],
|
|
347
|
+
"description": "Optional immutable-ref filter for action=list only.",
|
|
348
|
+
},
|
|
349
|
+
"page_size": {
|
|
350
|
+
"type": "integer",
|
|
351
|
+
"minimum": 1,
|
|
352
|
+
"maximum": MAX_PAGE_SIZE,
|
|
353
|
+
"description": "Bounded page size for action=list only.",
|
|
354
|
+
},
|
|
355
|
+
"cursor": {
|
|
356
|
+
"type": "string",
|
|
357
|
+
"maxLength": 256,
|
|
358
|
+
"description": "Opaque next_cursor from a prior action=list call.",
|
|
359
|
+
},
|
|
360
|
+
"id": {
|
|
361
|
+
"type": "string",
|
|
362
|
+
"pattern": "^(ev1|cov1|del1)_[0-9a-f]{64}$",
|
|
363
|
+
"description": "Stable record id returned by action=list; action=get only.",
|
|
364
|
+
},
|
|
310
365
|
},
|
|
311
366
|
},
|
|
312
367
|
"annotations": {"readOnlyHint": True, "destructiveHint": False, "openWorldHint": False},
|
{open_code_review_toolkit-0.6.0 → open_code_review_toolkit-0.6.2}/src/ocr_toolkit/evidence/model.py
RENAMED
|
@@ -42,6 +42,7 @@ class RefRole(str, Enum):
|
|
|
42
42
|
|
|
43
43
|
BASE = "base"
|
|
44
44
|
HEAD = "head"
|
|
45
|
+
POLICY = "policy"
|
|
45
46
|
SHARED = "shared"
|
|
46
47
|
|
|
47
48
|
|
|
@@ -283,7 +284,7 @@ class CoverageRecord:
|
|
|
283
284
|
or any(ord(character) < 32 for character in value)
|
|
284
285
|
):
|
|
285
286
|
raise ValueError(f"coverage {name} must contain between 1 and 256 safe characters")
|
|
286
|
-
if self.ref
|
|
287
|
+
if self.ref not in {RefRole.BASE, RefRole.HEAD}:
|
|
287
288
|
raise ValueError("coverage ref must be base or head")
|
|
288
289
|
if len(self.commit_sha) != 40 or not all(
|
|
289
290
|
character in "0123456789abcdef" for character in self.commit_sha
|
|
@@ -407,7 +408,9 @@ class EvidenceSnapshot:
|
|
|
407
408
|
"""Ensure snapshot records match the declared ref and are ordered."""
|
|
408
409
|
|
|
409
410
|
if self.ref is RefRole.SHARED:
|
|
410
|
-
raise ValueError("snapshot ref must be base or
|
|
411
|
+
raise ValueError("snapshot ref must be base, head, or policy")
|
|
412
|
+
if self.ref is RefRole.POLICY and self.coverage:
|
|
413
|
+
raise ValueError("policy snapshot cannot contain coverage")
|
|
411
414
|
if len(self.commit_sha) != 40 or not all(c in "0123456789abcdef" for c in self.commit_sha):
|
|
412
415
|
raise ValueError("snapshot commit_sha must be a lowercase 40-character SHA-1")
|
|
413
416
|
for record in self.records:
|
|
@@ -45,7 +45,7 @@ def is_legacy_policy_value(value: object) -> bool:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def validate_policy_record(kind: str, value: object) -> None:
|
|
48
|
-
"""Validate one
|
|
48
|
+
"""Validate one structured policy evidence value."""
|
|
49
49
|
|
|
50
50
|
outer = _exact_mapping(value, {"identity", "fact"}, kind)
|
|
51
51
|
if not policy_value_within_budget(outer):
|