open-code-review-toolkit 0.3.1__tar.gz → 0.4.1__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.3.1 → open_code_review_toolkit-0.4.1}/PKG-INFO +8 -12
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/README.md +6 -8
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/pyproject.toml +41 -21
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/__init__.py +10 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/cli.py +1 -13
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/common/git.py +53 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/config_writer.py +32 -4
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/__init__.py +35 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/__main__.py +14 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/ansible.py +193 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/ansible_requirements.py +280 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/artifacts.py +74 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/categorize.py +107 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/collect.py +119 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/collectors.py +764 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/composer_manifests.py +327 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/go_manifests.py +309 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/infrastructure.py +274 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/invocation.py +85 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/javascript_manifests.py +446 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/manifest_model.py +28 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/mcp.py +338 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/model.py +293 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/project.py +115 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/python_manifests.py +500 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/repository.py +521 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/evidence/store.py +398 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/mcp_config.py +152 -65
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/ocr_result.py +317 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/formatting.py +112 -21
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/posting/result.py +78 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/settings.py +21 -37
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/workflow.py +104 -20
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/preflight.py +1 -1
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/providers/__init__.py +1 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/providers/gitlab.py +38 -0
- open_code_review_toolkit-0.4.1/src/ocr_toolkit/review_runner.py +402 -0
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/__init__.py +0 -1
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/__main__.py +0 -8
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/ansible.py +0 -561
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/categorize.py +0 -162
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/instructions.py +0 -269
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/manifests.py +0 -471
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/planner.py +0 -227
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/render.py +0 -955
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/repo.py +0 -674
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/context/settings.py +0 -97
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/posting/__init__.py +0 -1
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/posting/result.py +0 -116
- open_code_review_toolkit-0.3.1/src/ocr_toolkit/review_runner.py +0 -117
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/.gitignore +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/LICENSE +0 -0
- {open_code_review_toolkit-0.3.1/src/ocr_toolkit → open_code_review_toolkit-0.4.1/src/ocr_toolkit/common}/__init__.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.3.1/src/ocr_toolkit/common → open_code_review_toolkit-0.4.1/src/ocr_toolkit/posting}/__init__.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/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.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -217,21 +217,19 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
217
217
|
Classifier: Operating System :: MacOS
|
|
218
218
|
Classifier: Operating System :: POSIX :: Linux
|
|
219
219
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
220
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
221
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
222
220
|
Classifier: Programming Language :: Python :: 3.12
|
|
223
221
|
Classifier: Programming Language :: Python :: 3.13
|
|
224
222
|
Classifier: Programming Language :: Python :: 3.14
|
|
225
223
|
Classifier: Typing :: Typed
|
|
226
|
-
Requires-Python: <3.15,>=3.
|
|
224
|
+
Requires-Python: <3.15,>=3.12
|
|
227
225
|
Description-Content-Type: text/markdown
|
|
228
226
|
|
|
229
227
|
# Open Code Review Toolkit
|
|
230
228
|
|
|
231
|
-
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
|
|
229
|
+
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
230
|
|
|
233
231
|
> [!NOTE]
|
|
234
|
-
> The project is under active development
|
|
232
|
+
> The project is under active development; the public API, CLI, environment contract, and generated schemas may evolve before 1.0.
|
|
235
233
|
|
|
236
234
|
## Install
|
|
237
235
|
|
|
@@ -243,9 +241,9 @@ ocr --version
|
|
|
243
241
|
ocr-ci --help
|
|
244
242
|
```
|
|
245
243
|
|
|
246
|
-
The current compatibility target is OCR `1.8.
|
|
244
|
+
The current compatibility target is OCR `1.8.2`. CI should pin the release and verify its published checksum before execution.
|
|
247
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.
|
|
248
|
-
Review output defaults to English.
|
|
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`.
|
|
249
247
|
|
|
250
248
|
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.
|
|
251
249
|
|
|
@@ -255,7 +253,7 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
255
253
|
|
|
256
254
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
257
255
|
|
|
258
|
-
Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the
|
|
256
|
+
Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the evidence collector supplies target-ref decisions to OCR and never lets a source change self-authorize its own review. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) for the entry format, inline marker convention, security boundary, and limitations.
|
|
259
257
|
|
|
260
258
|
## Project development
|
|
261
259
|
|
|
@@ -270,13 +268,11 @@ The project is evolving from bounded background generation toward a shared Repos
|
|
|
270
268
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
271
269
|
2. Pin and checksum the OCR binary.
|
|
272
270
|
3. Install this package.
|
|
273
|
-
4. Run the
|
|
271
|
+
4. Run the four public helper stages around `ocr review`:
|
|
274
272
|
|
|
275
273
|
```console
|
|
276
274
|
ocr-ci preflight
|
|
277
275
|
ocr-ci configure
|
|
278
|
-
ocr-ci mcp-config
|
|
279
|
-
ocr-ci context --output .review-context/dependencies.md
|
|
280
276
|
ocr-ci review --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log -- ... --format json
|
|
281
277
|
ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
282
278
|
```
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Open Code Review Toolkit
|
|
2
2
|
|
|
3
|
-
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
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
> The project is under active development
|
|
6
|
+
> The project is under active development; the public API, CLI, environment contract, and generated schemas may evolve before 1.0.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -15,9 +15,9 @@ 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.2`. 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
|
-
Review output defaults to English.
|
|
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
|
|
|
22
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.
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
27
27
|
|
|
28
28
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
29
29
|
|
|
30
|
-
Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the
|
|
30
|
+
Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/accepted-decisions.md`; the evidence collector supplies target-ref decisions to OCR and never lets a source change self-authorize its own review. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) for the entry format, inline marker convention, security boundary, and limitations.
|
|
31
31
|
|
|
32
32
|
## Project development
|
|
33
33
|
|
|
@@ -42,13 +42,11 @@ The project is evolving from bounded background generation toward a shared Repos
|
|
|
42
42
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
43
43
|
2. Pin and checksum the OCR binary.
|
|
44
44
|
3. Install this package.
|
|
45
|
-
4. Run the
|
|
45
|
+
4. Run the four public helper stages around `ocr review`:
|
|
46
46
|
|
|
47
47
|
```console
|
|
48
48
|
ocr-ci preflight
|
|
49
49
|
ocr-ci configure
|
|
50
|
-
ocr-ci mcp-config
|
|
51
|
-
ocr-ci context --output .review-context/dependencies.md
|
|
52
50
|
ocr-ci review --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log -- ... --format json
|
|
53
51
|
ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
54
52
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling>=1.
|
|
2
|
+
requires = ["hatchling>=1.31.0", "hatch-vcs>=0.5.0"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -7,7 +7,7 @@ name = "open-code-review-toolkit"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Unofficial GitLab CI integration layer for Open Code Review"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12,<3.15"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
12
|
authors = [{ name = "Open Code Review Toolkit contributors" }]
|
|
13
13
|
keywords = ["code-review", "gitlab", "ci", "open-code-review"]
|
|
@@ -18,8 +18,6 @@ classifiers = [
|
|
|
18
18
|
"Operating System :: MacOS",
|
|
19
19
|
"Operating System :: POSIX :: Linux",
|
|
20
20
|
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
-
"Programming Language :: Python :: 3.10",
|
|
22
|
-
"Programming Language :: Python :: 3.11",
|
|
23
21
|
"Programming Language :: Python :: 3.12",
|
|
24
22
|
"Programming Language :: Python :: 3.13",
|
|
25
23
|
"Programming Language :: Python :: 3.14",
|
|
@@ -39,16 +37,16 @@ ocr-ci = "ocr_toolkit.cli:main"
|
|
|
39
37
|
[dependency-groups]
|
|
40
38
|
dev = [
|
|
41
39
|
"bandit==1.9.4",
|
|
42
|
-
"build>=1.
|
|
43
|
-
"coverage[toml]>=7.
|
|
44
|
-
"hatch-vcs>=0.
|
|
45
|
-
"mypy>=
|
|
46
|
-
"pip-audit>=2.
|
|
47
|
-
"pytest>=
|
|
48
|
-
"pytest-cov>=
|
|
49
|
-
"ruff>=0.
|
|
50
|
-
"towncrier>=
|
|
51
|
-
"twine>=
|
|
40
|
+
"build>=1.5.0",
|
|
41
|
+
"coverage[toml]>=7.15.2",
|
|
42
|
+
"hatch-vcs>=0.5.0",
|
|
43
|
+
"mypy>=2.3.0",
|
|
44
|
+
"pip-audit>=2.10.1",
|
|
45
|
+
"pytest>=9.1.1",
|
|
46
|
+
"pytest-cov>=7.1.0",
|
|
47
|
+
"ruff>=0.16.0",
|
|
48
|
+
"towncrier>=25.8.0",
|
|
49
|
+
"twine>=7.0.0",
|
|
52
50
|
]
|
|
53
51
|
|
|
54
52
|
[tool.hatch.version]
|
|
@@ -67,13 +65,15 @@ only-include = ["src", "README.md", "LICENSE", "pyproject.toml"]
|
|
|
67
65
|
fallback_version = "0.3.0.dev0"
|
|
68
66
|
|
|
69
67
|
[tool.ruff]
|
|
70
|
-
target-version = "
|
|
68
|
+
target-version = "py312"
|
|
71
69
|
line-length = 100
|
|
72
70
|
src = ["src", "tests"]
|
|
73
71
|
|
|
74
72
|
[tool.ruff.lint]
|
|
75
73
|
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
76
|
-
|
|
74
|
+
# These upgrade rules describe a separate future modernization of the existing
|
|
75
|
+
# codebase; changing the minimum runtime must not silently rewrite public types.
|
|
76
|
+
ignore = ["E402", "E501", "E731", "B007", "B023", "B905", "SIM103", "SIM105", "SIM117", "RUF002", "RUF012", "UP017", "UP022", "UP040", "UP042", "UP045", "UP047"]
|
|
77
77
|
|
|
78
78
|
[tool.ruff.lint.per-file-ignores]
|
|
79
79
|
"tests/**/*.py" = ["S101"]
|
|
@@ -83,7 +83,7 @@ ignore = ["E402", "E501", "E731", "B007", "B023", "B905", "SIM103", "SIM105", "S
|
|
|
83
83
|
exclude = ["src/ocr_toolkit/_version.py"]
|
|
84
84
|
|
|
85
85
|
[tool.mypy]
|
|
86
|
-
python_version = "3.
|
|
86
|
+
python_version = "3.12"
|
|
87
87
|
strict = true
|
|
88
88
|
files = ["src/ocr_toolkit"]
|
|
89
89
|
exclude = ["src/ocr_toolkit/_version.py"]
|
|
@@ -124,12 +124,27 @@ underlines = ["", "", ""]
|
|
|
124
124
|
|
|
125
125
|
[[tool.towncrier.type]]
|
|
126
126
|
directory = "feature"
|
|
127
|
-
name = "Features"
|
|
127
|
+
name = "🚀 Features"
|
|
128
128
|
showcontent = true
|
|
129
129
|
|
|
130
130
|
[[tool.towncrier.type]]
|
|
131
131
|
directory = "bugfix"
|
|
132
|
-
name = "Bug
|
|
132
|
+
name = "🐛 Bug Fixes"
|
|
133
|
+
showcontent = true
|
|
134
|
+
|
|
135
|
+
[[tool.towncrier.type]]
|
|
136
|
+
directory = "refactor"
|
|
137
|
+
name = "🔧 Refactoring"
|
|
138
|
+
showcontent = true
|
|
139
|
+
|
|
140
|
+
[[tool.towncrier.type]]
|
|
141
|
+
directory = "doc"
|
|
142
|
+
name = "📖 Documentation"
|
|
143
|
+
showcontent = true
|
|
144
|
+
|
|
145
|
+
[[tool.towncrier.type]]
|
|
146
|
+
directory = "rules"
|
|
147
|
+
name = "🧩 Rules"
|
|
133
148
|
showcontent = true
|
|
134
149
|
|
|
135
150
|
[[tool.towncrier.type]]
|
|
@@ -138,6 +153,11 @@ name = "Security"
|
|
|
138
153
|
showcontent = true
|
|
139
154
|
|
|
140
155
|
[[tool.towncrier.type]]
|
|
141
|
-
directory = "
|
|
142
|
-
name = "
|
|
156
|
+
directory = "deprecation"
|
|
157
|
+
name = "Deprecations"
|
|
158
|
+
showcontent = true
|
|
159
|
+
|
|
160
|
+
[[tool.towncrier.type]]
|
|
161
|
+
directory = "removal"
|
|
162
|
+
name = "Removals"
|
|
143
163
|
showcontent = true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Open Code Review CI helper package and centralized runtime metadata."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from ocr_toolkit._version import __version__
|
|
5
|
+
except ModuleNotFoundError:
|
|
6
|
+
# Hatch-vcs creates ``_version.py`` for editable/build installs. A raw source
|
|
7
|
+
# checkout must remain importable without introducing another release version.
|
|
8
|
+
__version__ = "0+unknown"
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
{open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.4.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -8,7 +8,6 @@ from collections.abc import Sequence
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from ocr_toolkit import configure, mcp_config, preflight, review_runner
|
|
11
|
-
from ocr_toolkit.context import render as context_render
|
|
12
11
|
from ocr_toolkit.posting.workflow import main as posting_main
|
|
13
12
|
|
|
14
13
|
|
|
@@ -32,15 +31,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
32
31
|
"ocr_args", nargs=argparse.REMAINDER, help="OCR review arguments after --."
|
|
33
32
|
)
|
|
34
33
|
|
|
35
|
-
context_parser = subparsers.add_parser(
|
|
36
|
-
"context", help="Generate bounded repository review context."
|
|
37
|
-
)
|
|
38
|
-
context_parser.add_argument(
|
|
39
|
-
"--output",
|
|
40
|
-
default=".review-context/dependencies.md",
|
|
41
|
-
help="Output markdown path.",
|
|
42
|
-
)
|
|
43
|
-
|
|
44
34
|
post_parser = subparsers.add_parser("post", help="Publish an OCR result artifact to GitLab.")
|
|
45
35
|
post_parser.add_argument(
|
|
46
36
|
# The public GitLab example writes this fixed path inside an isolated CI job.
|
|
@@ -70,12 +60,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
70
60
|
if args.command == "review":
|
|
71
61
|
ocr_args = args.ocr_args[1:] if args.ocr_args[:1] == ["--"] else args.ocr_args
|
|
72
62
|
try:
|
|
73
|
-
return review_runner.
|
|
63
|
+
return review_runner.run_evidence_review(Path(args.result), Path(args.stderr), ocr_args)
|
|
74
64
|
except review_runner.ReviewRunnerError as exc:
|
|
75
65
|
print(f"Cannot run Open Code Review: {exc}", file=sys.stderr)
|
|
76
66
|
return 2
|
|
77
|
-
if args.command == "context":
|
|
78
|
-
return context_render.main(["--output", args.output])
|
|
79
67
|
if args.command == "post":
|
|
80
68
|
return posting_main([args.result, args.stderr])
|
|
81
69
|
raise AssertionError(f"unhandled command: {args.command}")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Shared trust-boundary controls for read-only Git plumbing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_PROCESS_OVERRIDE_KEYS = frozenset(
|
|
9
|
+
{
|
|
10
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
11
|
+
"GIT_COMMON_DIR",
|
|
12
|
+
"GIT_CONFIG_COUNT",
|
|
13
|
+
"GIT_CONFIG_PARAMETERS",
|
|
14
|
+
"GIT_DIR",
|
|
15
|
+
"GIT_INDEX_FILE",
|
|
16
|
+
"GIT_OBJECT_DIRECTORY",
|
|
17
|
+
"GIT_REPLACE_REF_BASE",
|
|
18
|
+
"GIT_WORK_TREE",
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def isolated_git_environment() -> dict[str, str]:
|
|
24
|
+
"""Return an environment isolated from caller-controlled Git state."""
|
|
25
|
+
|
|
26
|
+
environment = {
|
|
27
|
+
key: value
|
|
28
|
+
for key, value in os.environ.items()
|
|
29
|
+
if key not in _PROCESS_OVERRIDE_KEYS
|
|
30
|
+
and not key.startswith(("GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_"))
|
|
31
|
+
}
|
|
32
|
+
environment.update(
|
|
33
|
+
{
|
|
34
|
+
"GIT_CONFIG_GLOBAL": os.devnull,
|
|
35
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
36
|
+
"GIT_CONFIG_SYSTEM": os.devnull,
|
|
37
|
+
"GIT_NO_REPLACE_OBJECTS": "1",
|
|
38
|
+
"GIT_OPTIONAL_LOCKS": "0",
|
|
39
|
+
"GIT_PAGER": "cat",
|
|
40
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
41
|
+
"LC_ALL": "C",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
return environment
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_only_git_prefix(root: Path | None = None) -> list[str]:
|
|
48
|
+
"""Return Git arguments that bind a root and disable repository hooks."""
|
|
49
|
+
|
|
50
|
+
prefix = ["git", "-c", "core.hooksPath=/dev/null"]
|
|
51
|
+
if root is not None:
|
|
52
|
+
prefix.extend(["-C", str(root)])
|
|
53
|
+
return prefix
|
{open_code_review_toolkit-0.3.1 → open_code_review_toolkit-0.4.1}/src/ocr_toolkit/config_writer.py
RENAMED
|
@@ -10,6 +10,8 @@ from collections.abc import Mapping
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
+
MAX_OCR_CONFIG_BYTES = 1_000_000
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
class OCRConfigError(Exception):
|
|
15
17
|
"""OCR configuration could not be read or written safely."""
|
|
@@ -25,16 +27,40 @@ def read_ocr_config(path: Path | None = None) -> dict[str, Any]:
|
|
|
25
27
|
"""Read OCR config JSON, returning an empty config when it is absent."""
|
|
26
28
|
|
|
27
29
|
config_path = path or ocr_config_path()
|
|
28
|
-
|
|
30
|
+
flags = os.O_RDONLY
|
|
31
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
32
|
+
flags |= os.O_NOFOLLOW
|
|
33
|
+
descriptor = -1
|
|
34
|
+
try:
|
|
35
|
+
descriptor = os.open(config_path, flags)
|
|
36
|
+
except FileNotFoundError:
|
|
29
37
|
return {}
|
|
38
|
+
except OSError as exc:
|
|
39
|
+
raise OCRConfigError(f"cannot read OCR config: {exc}") from exc
|
|
30
40
|
try:
|
|
31
|
-
|
|
41
|
+
metadata = os.fstat(descriptor)
|
|
42
|
+
if not stat.S_ISREG(metadata.st_mode) or metadata.st_nlink != 1:
|
|
43
|
+
raise OCRConfigError("OCR config must be a private regular file with one link")
|
|
44
|
+
if metadata.st_size > MAX_OCR_CONFIG_BYTES:
|
|
45
|
+
raise OCRConfigError("OCR config exceeds the bounded byte limit")
|
|
46
|
+
chunks: list[bytes] = []
|
|
47
|
+
remaining = MAX_OCR_CONFIG_BYTES + 1
|
|
48
|
+
while remaining:
|
|
49
|
+
chunk = os.read(descriptor, min(64 * 1024, remaining))
|
|
50
|
+
if not chunk:
|
|
51
|
+
break
|
|
52
|
+
chunks.append(chunk)
|
|
53
|
+
remaining -= len(chunk)
|
|
54
|
+
raw = b"".join(chunks)
|
|
55
|
+
if len(raw) > MAX_OCR_CONFIG_BYTES:
|
|
56
|
+
raise OCRConfigError("OCR config exceeds the bounded byte limit")
|
|
57
|
+
data = json.loads(raw)
|
|
32
58
|
except json.JSONDecodeError as exc:
|
|
33
59
|
raise OCRConfigError(f"OCR config is not valid JSON: {exc}") from exc
|
|
34
60
|
except UnicodeDecodeError as exc:
|
|
35
61
|
raise OCRConfigError(f"OCR config is not valid UTF-8: {exc}") from exc
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
finally:
|
|
63
|
+
os.close(descriptor)
|
|
38
64
|
if not isinstance(data, dict):
|
|
39
65
|
raise OCRConfigError("OCR config top-level value is not an object")
|
|
40
66
|
return data
|
|
@@ -51,6 +77,8 @@ def write_ocr_config(config: Mapping[str, Any], path: Path | None = None) -> Non
|
|
|
51
77
|
raise OCRConfigError(f"cannot prepare OCR config directory: {exc}") from exc
|
|
52
78
|
|
|
53
79
|
payload = json.dumps(config, ensure_ascii=False, indent=4, sort_keys=True) + "\n"
|
|
80
|
+
if len(payload.encode("utf-8")) > MAX_OCR_CONFIG_BYTES:
|
|
81
|
+
raise OCRConfigError("OCR config exceeds the bounded byte limit")
|
|
54
82
|
tmp_name = ""
|
|
55
83
|
try:
|
|
56
84
|
with tempfile.NamedTemporaryFile(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Schema-versioned repository evidence contracts and projections."""
|
|
2
|
+
|
|
3
|
+
from ocr_toolkit.evidence.model import (
|
|
4
|
+
Confidence,
|
|
5
|
+
EvidenceDelta,
|
|
6
|
+
EvidenceRecord,
|
|
7
|
+
EvidenceSnapshot,
|
|
8
|
+
RefRole,
|
|
9
|
+
Sensitivity,
|
|
10
|
+
TrustClass,
|
|
11
|
+
)
|
|
12
|
+
from ocr_toolkit.evidence.repository import (
|
|
13
|
+
GitRepositoryReader,
|
|
14
|
+
RepositoryEvidenceError,
|
|
15
|
+
build_file_snapshot,
|
|
16
|
+
file_deltas,
|
|
17
|
+
)
|
|
18
|
+
from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError, EvidenceStoreLimits
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Confidence",
|
|
22
|
+
"EvidenceDelta",
|
|
23
|
+
"EvidenceRecord",
|
|
24
|
+
"EvidenceSnapshot",
|
|
25
|
+
"EvidenceStore",
|
|
26
|
+
"EvidenceStoreError",
|
|
27
|
+
"EvidenceStoreLimits",
|
|
28
|
+
"GitRepositoryReader",
|
|
29
|
+
"RefRole",
|
|
30
|
+
"RepositoryEvidenceError",
|
|
31
|
+
"Sensitivity",
|
|
32
|
+
"TrustClass",
|
|
33
|
+
"build_file_snapshot",
|
|
34
|
+
"file_deltas",
|
|
35
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Run the toolkit-owned repository evidence MCP server."""
|
|
2
|
+
|
|
3
|
+
from ocr_toolkit.evidence.artifacts import repository_artifacts
|
|
4
|
+
from ocr_toolkit.evidence.mcp import serve
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> int:
|
|
8
|
+
"""Serve the fixed private evidence store for the current repository."""
|
|
9
|
+
|
|
10
|
+
return serve(repository_artifacts().store)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Derive bounded Ansible topology facts from immutable repository blobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import PurePosixPath
|
|
8
|
+
|
|
9
|
+
MAX_TOPOLOGY_ITEMS = 256
|
|
10
|
+
PLAYBOOK_KEY_RE = re.compile(r"^(?P<indent>\s*)[\"']?(?P<key>hosts|import_playbook)[\"']?\s*:")
|
|
11
|
+
INI_GROUP_RE = re.compile(r"^\s*\[(?P<group>[^]#:]+)(?::children|:vars)?]\s*$")
|
|
12
|
+
YAML_GROUP_RE = re.compile(r"^(?P<indent>\s*)(?P<group>[A-Za-z0-9_.:-]+):(?:\s*\{\s*})?\s*$")
|
|
13
|
+
YAML_CHILDREN_RE = re.compile(r"^(?P<indent>\s*)children:\s*$")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class AnsibleTopologyFact:
|
|
18
|
+
"""Describe one normalized Ansible path or inventory-group fact."""
|
|
19
|
+
|
|
20
|
+
kind: str
|
|
21
|
+
identity: str
|
|
22
|
+
value: dict[str, str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def topology_candidate(path: str) -> bool:
|
|
26
|
+
"""Return whether a normalized path can contribute Ansible topology."""
|
|
27
|
+
|
|
28
|
+
parts = PurePosixPath(path).parts
|
|
29
|
+
if not parts:
|
|
30
|
+
return False
|
|
31
|
+
name = parts[-1].casefold()
|
|
32
|
+
return (
|
|
33
|
+
(len(parts) == 1 and name.endswith((".yml", ".yaml")))
|
|
34
|
+
or _role_surface(path) is not None
|
|
35
|
+
or _is_inventory_path(path)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _role_surface(path: str) -> tuple[str, str] | None:
|
|
40
|
+
"""Return role name and supported topology surface for a canonical role path."""
|
|
41
|
+
|
|
42
|
+
parts = PurePosixPath(path).parts
|
|
43
|
+
folded = tuple(part.casefold() for part in parts)
|
|
44
|
+
if (
|
|
45
|
+
len(parts) == 4
|
|
46
|
+
and folded[0] == "roles"
|
|
47
|
+
and folded[2] in {"meta", "defaults", "vars"}
|
|
48
|
+
and folded[3] in {"main.yml", "main.yaml"}
|
|
49
|
+
):
|
|
50
|
+
surface = "metadata" if folded[2] == "meta" else folded[2]
|
|
51
|
+
return parts[1], surface
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_inventory_path(path: str) -> bool:
|
|
56
|
+
"""Recognize conventional inventory paths while excluding variable payloads."""
|
|
57
|
+
|
|
58
|
+
pure = PurePosixPath(path)
|
|
59
|
+
parts = pure.parts
|
|
60
|
+
if not parts:
|
|
61
|
+
return False
|
|
62
|
+
name = parts[-1].casefold()
|
|
63
|
+
folded = tuple(part.casefold() for part in parts)
|
|
64
|
+
if "group_vars" in folded or "host_vars" in folded:
|
|
65
|
+
return False
|
|
66
|
+
return name in {
|
|
67
|
+
"hosts",
|
|
68
|
+
"hosts.ini",
|
|
69
|
+
"hosts.yml",
|
|
70
|
+
"hosts.yaml",
|
|
71
|
+
"inventory",
|
|
72
|
+
"inventory.ini",
|
|
73
|
+
"inventory.yml",
|
|
74
|
+
"inventory.yaml",
|
|
75
|
+
} or (
|
|
76
|
+
("inventory" in folded or "inventories" in folded)
|
|
77
|
+
and pure.suffix.casefold() in {"", ".ini", ".yml", ".yaml"}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_root_playbook(path: str, text: str) -> bool:
|
|
82
|
+
"""Recognize a root-level playbook from bounded YAML structure signals."""
|
|
83
|
+
|
|
84
|
+
pure = PurePosixPath(path)
|
|
85
|
+
if len(pure.parts) != 1 or pure.suffix.casefold() not in {".yml", ".yaml"}:
|
|
86
|
+
return False
|
|
87
|
+
top_level_list_item = False
|
|
88
|
+
play_child_indent: int | None = None
|
|
89
|
+
for raw_line in text.splitlines():
|
|
90
|
+
line = raw_line.rstrip()
|
|
91
|
+
stripped = line.strip()
|
|
92
|
+
if not stripped or stripped.startswith("#") or stripped in {"---", "..."}:
|
|
93
|
+
continue
|
|
94
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
95
|
+
if indent == 0 and stripped.startswith("-"):
|
|
96
|
+
top_level_list_item = True
|
|
97
|
+
play_child_indent = None
|
|
98
|
+
if PLAYBOOK_KEY_RE.match(stripped[1:].lstrip()):
|
|
99
|
+
return True
|
|
100
|
+
continue
|
|
101
|
+
if indent == 0:
|
|
102
|
+
top_level_list_item = False
|
|
103
|
+
play_child_indent = None
|
|
104
|
+
continue
|
|
105
|
+
if not top_level_list_item:
|
|
106
|
+
continue
|
|
107
|
+
if play_child_indent is None:
|
|
108
|
+
play_child_indent = indent
|
|
109
|
+
if indent == play_child_indent and PLAYBOOK_KEY_RE.match(stripped):
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _inventory_groups(text: str) -> tuple[str, ...]:
|
|
115
|
+
"""Extract bounded INI and YAML inventory group names without a YAML runtime."""
|
|
116
|
+
|
|
117
|
+
groups: set[str] = set()
|
|
118
|
+
children_indent: int | None = None
|
|
119
|
+
group_indent: int | None = None
|
|
120
|
+
top_level_group: str | None = None
|
|
121
|
+
for raw_line in text.splitlines():
|
|
122
|
+
ini = INI_GROUP_RE.match(raw_line)
|
|
123
|
+
if ini:
|
|
124
|
+
groups.add(ini.group("group").strip())
|
|
125
|
+
if len(groups) >= MAX_TOPOLOGY_ITEMS:
|
|
126
|
+
break
|
|
127
|
+
continue
|
|
128
|
+
children = YAML_CHILDREN_RE.match(raw_line)
|
|
129
|
+
if children:
|
|
130
|
+
children_indent = len(children.group("indent"))
|
|
131
|
+
group_indent = None
|
|
132
|
+
continue
|
|
133
|
+
yaml_group = YAML_GROUP_RE.match(raw_line)
|
|
134
|
+
if yaml_group and len(yaml_group.group("indent")) == 0:
|
|
135
|
+
group = yaml_group.group("group")
|
|
136
|
+
top_level_group = group if group not in {"all", "ungrouped"} else None
|
|
137
|
+
children_indent = None
|
|
138
|
+
group_indent = None
|
|
139
|
+
continue
|
|
140
|
+
if (
|
|
141
|
+
yaml_group
|
|
142
|
+
and top_level_group is not None
|
|
143
|
+
and yaml_group.group("group") in {"hosts", "children", "vars"}
|
|
144
|
+
):
|
|
145
|
+
groups.add(top_level_group)
|
|
146
|
+
if yaml_group.group("group") == "children":
|
|
147
|
+
children_indent = len(yaml_group.group("indent"))
|
|
148
|
+
group_indent = None
|
|
149
|
+
continue
|
|
150
|
+
if yaml_group and children_indent is not None:
|
|
151
|
+
indent = len(yaml_group.group("indent"))
|
|
152
|
+
if indent <= children_indent:
|
|
153
|
+
children_indent = None
|
|
154
|
+
group_indent = None
|
|
155
|
+
else:
|
|
156
|
+
# YAML indentation width is repository-defined; the first key
|
|
157
|
+
# below children establishes the sibling group depth.
|
|
158
|
+
if group_indent is None:
|
|
159
|
+
group_indent = indent
|
|
160
|
+
if indent == group_indent:
|
|
161
|
+
groups.add(yaml_group.group("group"))
|
|
162
|
+
if len(groups) >= MAX_TOPOLOGY_ITEMS:
|
|
163
|
+
break
|
|
164
|
+
return tuple(sorted(groups))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def collect_topology(path: str, text: str) -> tuple[AnsibleTopologyFact, ...]:
|
|
168
|
+
"""Collect typed Ansible topology facts for one immutable path and blob."""
|
|
169
|
+
|
|
170
|
+
facts = []
|
|
171
|
+
if _is_root_playbook(path, text):
|
|
172
|
+
facts.append(AnsibleTopologyFact("ansible.playbook", path, {"path": path, "scope": "root"}))
|
|
173
|
+
role_surface = _role_surface(path)
|
|
174
|
+
if role_surface is not None:
|
|
175
|
+
role, surface = role_surface
|
|
176
|
+
facts.append(
|
|
177
|
+
AnsibleTopologyFact(
|
|
178
|
+
f"ansible.role_{surface}",
|
|
179
|
+
path,
|
|
180
|
+
{"role": role, "path": path},
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
if _is_inventory_path(path):
|
|
184
|
+
facts.append(AnsibleTopologyFact("ansible.inventory", path, {"path": path}))
|
|
185
|
+
facts.extend(
|
|
186
|
+
AnsibleTopologyFact(
|
|
187
|
+
"ansible.inventory_group",
|
|
188
|
+
f"{path}:{group}",
|
|
189
|
+
{"path": path, "group": group},
|
|
190
|
+
)
|
|
191
|
+
for group in _inventory_groups(text)
|
|
192
|
+
)
|
|
193
|
+
return tuple(facts)
|