claude-dev-env 8.6.1 → 8.7.0
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.
- package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +1 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +3 -2
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +35 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +10 -9
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -0
- package/bin/install.test.mjs +25 -0
- package/docs/high-trust-agent-delivery.md +132 -0
- package/hooks/advisory/refactor_guard.py +2 -2
- package/hooks/advisory/test_refactor_guard.py +21 -0
- package/hooks/blocking/code_rules_enforcer_test_support.py +10 -0
- package/hooks/blocking/code_rules_shared.py +4 -7
- package/hooks/blocking/conftest.py +22 -0
- package/hooks/blocking/state_description_blocker.py +10 -8
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +2 -11
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +4 -29
- package/hooks/blocking/test_code_rules_enforcer_test_support.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +3 -19
- package/hooks/blocking/test_code_rules_shared.py +18 -0
- package/hooks/blocking/test_conftest.py +35 -0
- package/hooks/blocking/test_state_description_blocker.py +16 -0
- package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -14
- package/hooks/git-hooks/pull_request_handoff.py +2 -2
- package/hooks/git-hooks/test_config.py +10 -3
- package/hooks/git-hooks/test_pull_request_handoff.py +49 -0
- package/hooks/git-hooks/test_verification_notice_state.py +50 -0
- package/hooks/git-hooks/verification_notice_state.py +3 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +13 -5
- package/hooks/hooks_constants/refactor_guard_constants.py +2 -0
- package/hooks/hooks_constants/session_edit_stage_gate_constants.py +3 -4
- package/hooks/hooks_constants/state_description_blocker_constants.py +0 -3
- package/hooks/hooks_constants/test_state_description_blocker_constants.py +9 -7
- package/hooks/validators/exempt_paths.py +3 -18
- package/hooks/validators/test_exempt_paths.py +20 -0
- package/hooks/validators/test_python_antipattern_checks.py +14 -0
- package/package.json +1 -1
- package/rules/agent-merges-its-own-green-pull-request.md +54 -0
- package/rules/archiving-agent-config.md +10 -0
- package/rules/code-standards.md +1 -0
- package/rules/correction-lens.md +134 -0
- package/rules/flag-non-breaking-findings.md +11 -0
- package/rules/no-contrast-framing.md +68 -0
- package/rules/review-closure-is-a-check.md +44 -0
- package/scripts/Invoke-PesterSuites.ps1 +42 -0
- package/scripts/agent_merge_check.py +381 -0
- package/scripts/check.ps1 +12 -1
- package/scripts/ci/install-playtest.mjs +291 -0
- package/scripts/ci/install-playtest.test.mjs +84 -0
- package/scripts/ci/installer-lifecycle.mjs +11 -30
- package/scripts/ci/installer-lifecycle.test.mjs +14 -0
- package/scripts/ci/pstack-command-shim.test.mjs +33 -0
- package/scripts/ci/scratch-home-install.mjs +73 -0
- package/scripts/ci/scratch-home-install.test.mjs +49 -0
- package/scripts/codex_compat_materializer.py +33 -16
- package/scripts/contrast_framing.py +101 -0
- package/scripts/dev_env_scripts_constants/agent_merge_check_constants.py +121 -0
- package/scripts/dev_env_scripts_constants/contrast_framing_constants.py +82 -0
- package/scripts/dev_env_scripts_constants/merge_gate_check_constants.py +23 -0
- package/scripts/dev_env_scripts_constants/review_closure_constants.py +241 -0
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +12 -0
- package/scripts/dev_env_scripts_constants/test_contrast_framing_constants.py +60 -0
- package/scripts/durable_post_lint.py +30 -4
- package/scripts/followup_cli.py +6 -10
- package/scripts/merge_gate_checks.py +236 -0
- package/scripts/policy_lint/adapter_contrast_framing.py +59 -0
- package/scripts/policy_lint/adapters.py +30 -0
- package/scripts/policy_lint/config/constants.py +2 -0
- package/scripts/policy_lint/registry.py +5 -0
- package/scripts/repository_checks/config/constants.py +12 -0
- package/scripts/repository_checks/config/policy_document.py +28 -2
- package/scripts/repository_checks/followups.py +54 -0
- package/scripts/repository_checks/match_exemptions.py +67 -0
- package/scripts/repository_checks/models.py +46 -2
- package/scripts/repository_checks/reporting.py +23 -7
- package/scripts/repository_checks/tracked_secrets.py +2 -2
- package/scripts/repository_policy.py +4 -2
- package/scripts/review_closure.py +134 -0
- package/scripts/review_closure_github.py +345 -0
- package/scripts/review_closure_model.py +360 -0
- package/scripts/test_agent_merge_check.py +317 -0
- package/scripts/test_durable_post_lint.py +46 -0
- package/scripts/test_followup_cli.py +40 -0
- package/scripts/test_merge_gate_checks.py +92 -0
- package/scripts/tests/test_code_review_constants.py +78 -0
- package/scripts/tests/test_codex_compat_materializer.py +48 -4
- package/scripts/tests/test_contrast_framing.py +117 -0
- package/scripts/tests/test_followups.py +88 -0
- package/scripts/tests/test_match_exemptions.py +291 -0
- package/scripts/tests/test_models.py +67 -2
- package/scripts/tests/test_package_inventory.py +6 -4
- package/scripts/tests/test_pester_suites_run.py +53 -0
- package/scripts/tests/test_policy_document.py +39 -1
- package/scripts/tests/test_policy_lint_rules_contrast_framing.py +68 -0
- package/scripts/tests/test_reporting.py +18 -0
- package/scripts/tests/test_repository_policy.py +43 -1
- package/scripts/tests/test_review_closure.py +126 -0
- package/scripts/tests/test_review_closure_github.py +240 -0
- package/scripts/tests/test_review_closure_model.py +299 -0
- package/scripts/tests/test_tracked_secrets.py +42 -0
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +0 -12
- package/scripts/repository_checks/email_exemptions.py +0 -39
- package/scripts/tests/test_email_exemptions.py +0 -158
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Load the scanner matches a repository owns through its policy document."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from repository_checks.config.policy_document import (
|
|
8
|
+
ALL_MATCH_EXEMPTION_FAMILIES,
|
|
9
|
+
MatchExemptionFamily,
|
|
10
|
+
)
|
|
11
|
+
from repository_checks.policy_document import (
|
|
12
|
+
PolicyConfigurationRunFatal,
|
|
13
|
+
entry_path_and_digest,
|
|
14
|
+
read_policy_entries,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_match_exemptions(
|
|
19
|
+
repository_root: Path, family: MatchExemptionFamily
|
|
20
|
+
) -> frozenset[tuple[str, str, str]]:
|
|
21
|
+
"""Read one family of matches the repository owns.
|
|
22
|
+
|
|
23
|
+
::
|
|
24
|
+
|
|
25
|
+
the email family over a document owning one address
|
|
26
|
+
ok: {("contacts.py", "email", "<digest of the address>")}
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
repository_root: Repository containing the optional policy configuration.
|
|
30
|
+
family: Document field, scanner category, and rejection subject to read.
|
|
31
|
+
Returns:
|
|
32
|
+
Exact path, scanner category, and digest identities.
|
|
33
|
+
Raises:
|
|
34
|
+
ValueError: Configuration contains invalid JSON or schema fields.
|
|
35
|
+
OSError: Configuration cannot be read.
|
|
36
|
+
"""
|
|
37
|
+
exemptions: set[tuple[str, str, str]] = set()
|
|
38
|
+
for each_entry in read_policy_entries(repository_root, family.field_name):
|
|
39
|
+
relative_path, digest = entry_path_and_digest(each_entry, family.subject)
|
|
40
|
+
identity = (relative_path, family.category, digest)
|
|
41
|
+
if identity in exemptions:
|
|
42
|
+
raise PolicyConfigurationRunFatal(
|
|
43
|
+
f"Duplicate repository {family.subject.lower()}"
|
|
44
|
+
)
|
|
45
|
+
exemptions.add(identity)
|
|
46
|
+
return frozenset(exemptions)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_all_match_exemptions(
|
|
50
|
+
repository_root: Path,
|
|
51
|
+
) -> frozenset[tuple[str, str, str]]:
|
|
52
|
+
"""Read every family of matches the repository owns.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
repository_root: Repository containing the optional policy configuration.
|
|
56
|
+
Returns:
|
|
57
|
+
Exact path, scanner category, and digest identities across all families.
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: Configuration contains invalid JSON or schema fields.
|
|
60
|
+
OSError: Configuration cannot be read.
|
|
61
|
+
"""
|
|
62
|
+
return frozenset().union(
|
|
63
|
+
*(
|
|
64
|
+
load_match_exemptions(repository_root, each_family)
|
|
65
|
+
for each_family in ALL_MATCH_EXEMPTION_FAMILIES
|
|
66
|
+
)
|
|
67
|
+
)
|
|
@@ -7,10 +7,28 @@ from dataclasses import dataclass
|
|
|
7
7
|
from repository_checks.config.constants import (
|
|
8
8
|
FAILED_CHECK_EXIT_CODE,
|
|
9
9
|
FINDINGS_EXIT_CODE,
|
|
10
|
+
SEVERITY_BREAKING,
|
|
11
|
+
SEVERITY_BY_CHECK_ID,
|
|
12
|
+
SEVERITY_SMELL,
|
|
10
13
|
SUCCESS_EXIT_CODE,
|
|
11
14
|
)
|
|
12
15
|
|
|
13
16
|
|
|
17
|
+
def severity_for_check_id(check_id: str) -> str:
|
|
18
|
+
"""Return the severity a check identifier declares.
|
|
19
|
+
|
|
20
|
+
A check identifier absent from the table reads as breaking, so a check
|
|
21
|
+
added without a severity row fails the tree rather than passing quietly.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
check_id: Stable identifier of the check that raised a finding.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Either SEVERITY_BREAKING or SEVERITY_SMELL.
|
|
28
|
+
"""
|
|
29
|
+
return SEVERITY_BY_CHECK_ID.get(check_id, SEVERITY_BREAKING)
|
|
30
|
+
|
|
31
|
+
|
|
14
32
|
@dataclass(frozen=True)
|
|
15
33
|
class RepositoryFinding:
|
|
16
34
|
"""One committed-tree finding with a stable check id and relative path."""
|
|
@@ -19,19 +37,45 @@ class RepositoryFinding:
|
|
|
19
37
|
relative_path: str
|
|
20
38
|
message: str
|
|
21
39
|
|
|
40
|
+
@property
|
|
41
|
+
def severity(self) -> str:
|
|
42
|
+
"""Return the severity this finding's check identifier declares."""
|
|
43
|
+
return severity_for_check_id(self.check_id)
|
|
44
|
+
|
|
22
45
|
|
|
23
46
|
@dataclass(frozen=True)
|
|
24
47
|
class RepositoryCheckReport:
|
|
25
|
-
"""Sorted findings and the check ids that failed while they ran.
|
|
48
|
+
"""Sorted findings and the check ids that failed while they ran.
|
|
49
|
+
|
|
50
|
+
Splits its findings into the breaking ones and the advisory ones, and
|
|
51
|
+
derives the exit code from the breaking ones alone.
|
|
52
|
+
"""
|
|
26
53
|
|
|
27
54
|
all_findings: tuple[RepositoryFinding, ...]
|
|
28
55
|
all_failed_check_ids: tuple[str, ...]
|
|
29
56
|
|
|
57
|
+
@property
|
|
58
|
+
def all_breaking_findings(self) -> tuple[RepositoryFinding, ...]:
|
|
59
|
+
"""Return the findings that fail the tree."""
|
|
60
|
+
return self._findings_of_severity(SEVERITY_BREAKING)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def all_smell_findings(self) -> tuple[RepositoryFinding, ...]:
|
|
64
|
+
"""Return the findings that report and leave the tree passing."""
|
|
65
|
+
return self._findings_of_severity(SEVERITY_SMELL)
|
|
66
|
+
|
|
30
67
|
@property
|
|
31
68
|
def exit_code(self) -> int:
|
|
32
69
|
"""Return the process status for this report."""
|
|
33
70
|
if self.all_failed_check_ids:
|
|
34
71
|
return FAILED_CHECK_EXIT_CODE
|
|
35
|
-
if self.
|
|
72
|
+
if self.all_breaking_findings:
|
|
36
73
|
return FINDINGS_EXIT_CODE
|
|
37
74
|
return SUCCESS_EXIT_CODE
|
|
75
|
+
|
|
76
|
+
def _findings_of_severity(self, severity: str) -> tuple[RepositoryFinding, ...]:
|
|
77
|
+
return tuple(
|
|
78
|
+
each_finding
|
|
79
|
+
for each_finding in self.all_findings
|
|
80
|
+
if each_finding.severity == severity
|
|
81
|
+
)
|
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from repository_checks.config import constants as repository_constants
|
|
6
|
-
from repository_checks.models import RepositoryCheckReport
|
|
6
|
+
from repository_checks.models import RepositoryCheckReport, RepositoryFinding
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def render_report(report: RepositoryCheckReport) -> str:
|
|
10
10
|
"""Render findings and failed checks as newline-terminated text.
|
|
11
11
|
|
|
12
|
+
An advisory finding carries an ``advisory:`` prefix, so a reader of a
|
|
13
|
+
passing run can tell the reported line from one that failed the tree.
|
|
14
|
+
|
|
15
|
+
::
|
|
16
|
+
|
|
17
|
+
claude-md-orphans: notes/CLAUDE.md: references missing file x.py
|
|
18
|
+
advisory: package-inventory: pipeline/seam.py: production file is
|
|
19
|
+
absent from package inventory
|
|
20
|
+
|
|
12
21
|
Args:
|
|
13
22
|
report: Findings and failed check identifiers.
|
|
14
23
|
|
|
@@ -16,12 +25,7 @@ def render_report(report: RepositoryCheckReport) -> str:
|
|
|
16
25
|
The report body, or an empty string when the tree is clean.
|
|
17
26
|
"""
|
|
18
27
|
all_lines = [
|
|
19
|
-
|
|
20
|
-
check_id=each_finding.check_id,
|
|
21
|
-
relative_path=each_finding.relative_path,
|
|
22
|
-
message=each_finding.message,
|
|
23
|
-
)
|
|
24
|
-
for each_finding in report.all_findings
|
|
28
|
+
_finding_line(each_finding) for each_finding in report.all_findings
|
|
25
29
|
]
|
|
26
30
|
all_lines.extend(_failed_check_lines(report))
|
|
27
31
|
if not all_lines:
|
|
@@ -32,6 +36,18 @@ def render_report(report: RepositoryCheckReport) -> str:
|
|
|
32
36
|
)
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
def _finding_line(finding: RepositoryFinding) -> str:
|
|
40
|
+
if finding.severity == repository_constants.SEVERITY_SMELL:
|
|
41
|
+
line_template = repository_constants.ADVISORY_FINDING_LINE_TEMPLATE
|
|
42
|
+
else:
|
|
43
|
+
line_template = repository_constants.FINDING_LINE_TEMPLATE
|
|
44
|
+
return line_template.format(
|
|
45
|
+
check_id=finding.check_id,
|
|
46
|
+
relative_path=finding.relative_path,
|
|
47
|
+
message=finding.message,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
35
51
|
def _failed_check_lines(report: RepositoryCheckReport) -> list[str]:
|
|
36
52
|
return [
|
|
37
53
|
repository_constants.RULE_FAILED_LINE_TEMPLATE.format(check_id=each_check_id)
|
|
@@ -10,8 +10,8 @@ from types import ModuleType
|
|
|
10
10
|
from policy_lint.config import constants as policy_constants
|
|
11
11
|
|
|
12
12
|
from repository_checks.config import constants as repository_constants
|
|
13
|
-
from repository_checks.email_exemptions import load_email_exemptions
|
|
14
13
|
from repository_checks.hook_modules import load_hooks_module
|
|
14
|
+
from repository_checks.match_exemptions import load_all_match_exemptions
|
|
15
15
|
from repository_checks.models import RepositoryFinding
|
|
16
16
|
from repository_checks.path_exemptions import load_path_exemptions
|
|
17
17
|
|
|
@@ -31,7 +31,7 @@ def collect_tracked_secret_findings(
|
|
|
31
31
|
scanner = load_hooks_module(repository_constants.PII_SCANNER_MODULE_NAME)
|
|
32
32
|
exemption = load_hooks_module(repository_constants.REPOSITORY_EXEMPTION_MODULE_NAME)
|
|
33
33
|
all_allowlisted_literals = exemption.repository_allowlisted_values(repository_root)
|
|
34
|
-
all_exact_exemptions =
|
|
34
|
+
all_exact_exemptions = load_all_match_exemptions(repository_root)
|
|
35
35
|
all_exact_exemptions |= repository_constants.ALL_TRACKED_SECRET_EXACT_EXEMPTIONS
|
|
36
36
|
all_path_exemptions = load_path_exemptions(repository_root)
|
|
37
37
|
all_findings: list[RepositoryFinding] = []
|
|
@@ -16,6 +16,7 @@ from repository_checks.config.constants import (
|
|
|
16
16
|
REPOSITORY_ROOT_FLAG,
|
|
17
17
|
USAGE_EXIT_CODE,
|
|
18
18
|
)
|
|
19
|
+
from repository_checks.followups import record_smell_findings
|
|
19
20
|
from repository_checks.reporting import render_report
|
|
20
21
|
from repository_checks.runner import run_repository_checks
|
|
21
22
|
|
|
@@ -90,8 +91,8 @@ def main(
|
|
|
90
91
|
stderr: Error stream.
|
|
91
92
|
|
|
92
93
|
Returns:
|
|
93
|
-
Zero when the tree
|
|
94
|
-
and three when a check fails closed.
|
|
94
|
+
Zero when the tree carries no breaking finding, one for a breaking
|
|
95
|
+
finding, two for usage errors, and three when a check fails closed.
|
|
95
96
|
"""
|
|
96
97
|
try:
|
|
97
98
|
namespace = _build_parser().parse_args(list(all_arguments))
|
|
@@ -102,6 +103,7 @@ def main(
|
|
|
102
103
|
if namespace.repository_root is not None:
|
|
103
104
|
selected_root = Path(namespace.repository_root).resolve()
|
|
104
105
|
report = run_repository_checks(selected_root)
|
|
106
|
+
record_smell_findings(selected_root, report)
|
|
105
107
|
stdout.write(render_report(report))
|
|
106
108
|
return report.exit_code
|
|
107
109
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Report whether every review finding on a pull request head is answered.
|
|
3
|
+
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
python3 review_closure.py jl-cmd/claude-dev-env 1442
|
|
7
|
+
CLOSED jl-cmd/claude-dev-env#1442 ab845eb :: every review finding on this
|
|
8
|
+
commit is answered
|
|
9
|
+
|
|
10
|
+
A finding waits until the agent driving the pull request replies to it or
|
|
11
|
+
pushes the fix. Exit status is 0 for CLOSED, 1 for OPEN, and 2 when the pull
|
|
12
|
+
request state could not be read.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
from dev_env_scripts_constants.review_closure_constants import (
|
|
23
|
+
CLOSED_EXIT_CODE,
|
|
24
|
+
COMMAND_DESCRIPTION,
|
|
25
|
+
DRIVER_LOGIN_ARGUMENT_HELP,
|
|
26
|
+
ERROR_EXIT_CODE,
|
|
27
|
+
FINDING_LINE_TEMPLATE,
|
|
28
|
+
NUMBER_ARGUMENT_HELP,
|
|
29
|
+
OPEN_EXIT_CODE,
|
|
30
|
+
SLUG_ARGUMENT_HELP,
|
|
31
|
+
)
|
|
32
|
+
from pr_verification.github_parsing import GitHubError
|
|
33
|
+
from review_closure_github import (
|
|
34
|
+
github_token,
|
|
35
|
+
read_approvals_conclusion,
|
|
36
|
+
read_pull_request,
|
|
37
|
+
read_review_threads,
|
|
38
|
+
)
|
|
39
|
+
from review_closure_model import (
|
|
40
|
+
OpenFinding,
|
|
41
|
+
all_open_findings,
|
|
42
|
+
driver_logins,
|
|
43
|
+
head_sha,
|
|
44
|
+
verdict_line,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class ClosureReport:
|
|
50
|
+
"""What one run of this command found on a pull request."""
|
|
51
|
+
|
|
52
|
+
verdict: str
|
|
53
|
+
all_findings: tuple[OpenFinding, ...]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
57
|
+
"""Build the command line this script accepts.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A parser taking a repository slug, a pull request number, and any
|
|
61
|
+
number of extra driver logins.
|
|
62
|
+
"""
|
|
63
|
+
parser = argparse.ArgumentParser(description=COMMAND_DESCRIPTION)
|
|
64
|
+
parser.add_argument("slug", help=SLUG_ARGUMENT_HELP)
|
|
65
|
+
parser.add_argument("number", type=int, help=NUMBER_ARGUMENT_HELP)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--driver-login",
|
|
68
|
+
action="append",
|
|
69
|
+
default=[],
|
|
70
|
+
help=DRIVER_LOGIN_ARGUMENT_HELP,
|
|
71
|
+
)
|
|
72
|
+
return parser
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def closure_report(
|
|
76
|
+
slug: str, number: int, all_extra_logins: Sequence[str], token: str
|
|
77
|
+
) -> ClosureReport:
|
|
78
|
+
"""Read one pull request and judge the findings on its head.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
slug: The repository as ``owner/name``.
|
|
82
|
+
number: The pull request number.
|
|
83
|
+
all_extra_logins: Logins that count as the driving agent beside the
|
|
84
|
+
account that opened the pull request.
|
|
85
|
+
token: The GitHub token the reads authenticate with.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The verdict line and each finding that waits.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
GitHubError: The pull request state could not be read.
|
|
92
|
+
"""
|
|
93
|
+
all_pull_request_fields = read_pull_request(slug, number, token)
|
|
94
|
+
all_findings = all_open_findings(
|
|
95
|
+
read_review_threads(slug, number, token),
|
|
96
|
+
driver_logins(all_pull_request_fields, all_extra_logins),
|
|
97
|
+
read_approvals_conclusion(slug, head_sha(all_pull_request_fields), token),
|
|
98
|
+
)
|
|
99
|
+
return ClosureReport(
|
|
100
|
+
verdict=verdict_line(slug, all_pull_request_fields, all_findings),
|
|
101
|
+
all_findings=all_findings,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main(all_arguments: Sequence[str]) -> int:
|
|
106
|
+
"""Print the review closure verdict for one pull request.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
all_arguments: The command line, without the program name.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Zero when every finding is answered, one when a finding waits, and
|
|
113
|
+
two when the pull request state could not be read.
|
|
114
|
+
"""
|
|
115
|
+
parsed = build_parser().parse_args(all_arguments)
|
|
116
|
+
try:
|
|
117
|
+
report = closure_report(
|
|
118
|
+
parsed.slug, parsed.number, parsed.driver_login, github_token()
|
|
119
|
+
)
|
|
120
|
+
except GitHubError as failure:
|
|
121
|
+
print(failure, file=sys.stderr)
|
|
122
|
+
return ERROR_EXIT_CODE
|
|
123
|
+
print(report.verdict)
|
|
124
|
+
for each_finding in report.all_findings:
|
|
125
|
+
print(
|
|
126
|
+
FINDING_LINE_TEMPLATE.format(
|
|
127
|
+
subject=each_finding.subject, reason=each_finding.reason
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
return OPEN_EXIT_CODE if report.all_findings else CLOSED_EXIT_CODE
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
raise SystemExit(main(sys.argv[1:]))
|