CI ran .gitea/ci/commit_message_lint.py, an independent reimplementation that parsed the type/emoji mapping out of docs/common/commit_message.md. Its scope pattern accepted fix(-a), fix(a-), fix(a--b) and fix(_), and it had no subject length check, so the gate that actually blocks merges enforced weaker rules than the Validation this skill reports. The CI entry is now a wrapper that locates the skill validator and delegates to it with no arguments; commit_policy.json becomes the only rule source and explanatory docs stop being a machine policy input. The validator also learns the workflow_run event, whose payload carries neither a PR title nor a before/after range. An upstream pull_request now validates <integration-branch>..head_sha instead of HEAD alone, taking the branch name from COMMIT_LINT_MAIN_BRANCH, and degrades to the upstream head commit with a WARN rather than guessing a base. Alongside: --help now documents the no-argument CI mode it had always supported silently, CI wiring detail moves to references/ci-wiring.md, and the description gains negative boundaries. test/test_commit_message_policy.py asserts policy/spec-table equality and uses ast to assert the CI entry imports no regex and reads no file, so a second implementation cannot reappear unnoticed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.1 KiB
Python
81 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""CI entry point for commit-message lint.
|
|
|
|
This file only locates and delegates. Every rule, the type/emoji mapping and
|
|
the policy schema belong to the commit-message skill validator, which reads
|
|
`skills/commit-message/references/commit_policy.json`.
|
|
|
|
Do not reimplement validation here and do not parse rules out of
|
|
`docs/common/commit_message.md`: that document is human guidance, not a machine
|
|
policy source. See `skills/commit-message/references/ci-wiring.md`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
|
|
VALIDATOR_CANDIDATES = (
|
|
("skills", "commit-message", "scripts", "validate_commit_message.py"),
|
|
(
|
|
"docs",
|
|
"standards",
|
|
"playbook",
|
|
"skills",
|
|
"commit-message",
|
|
"scripts",
|
|
"validate_commit_message.py",
|
|
),
|
|
)
|
|
|
|
|
|
def _eprint(*args: object) -> None:
|
|
print(*args, file=sys.stderr)
|
|
|
|
|
|
def _repo_root() -> pathlib.Path:
|
|
output = subprocess.check_output(
|
|
["git", "rev-parse", "--show-toplevel"], text=True
|
|
)
|
|
return pathlib.Path(output.strip())
|
|
|
|
|
|
def _find_validator(root: pathlib.Path) -> pathlib.Path:
|
|
tried: list[pathlib.Path] = []
|
|
for parts in VALIDATOR_CANDIDATES:
|
|
path = root.joinpath(*parts)
|
|
tried.append(path)
|
|
if path.is_file():
|
|
return path
|
|
raise FileNotFoundError(
|
|
"commit-message skill validator not found; expected one of:\n"
|
|
+ "\n".join(f"- {path}" for path in tried)
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
try:
|
|
root = _repo_root()
|
|
except Exception as exc:
|
|
_eprint(f"ERROR: not a git repository: {exc}")
|
|
return 2
|
|
|
|
os.chdir(root)
|
|
|
|
try:
|
|
validator = _find_validator(root)
|
|
except FileNotFoundError as exc:
|
|
_eprint(f"ERROR: {exc}")
|
|
return 2
|
|
|
|
print(f"validator: {validator}")
|
|
sys.stdout.flush()
|
|
# No arguments: the validator selects its CI input mode from the event
|
|
# payload. Exit codes pass through unchanged (0 pass / 1 lint / 2 input).
|
|
return subprocess.run([sys.executable, str(validator)], cwd=root).returncode
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|