399ed93dc8
Replaces cli.sh + lib/*.sh with a claude_bottle/ Python package and a
cli.py entry point. No external dependencies — uses only Python's
stdlib (json, subprocess, getpass, tempfile, argparse, re, etc.).
- claude_bottle/{log,docker,manifest,env_resolve,network,pipelock,
skills,ssh,cli}.py mirror the previous lib/*.sh modules.
- Tests converted to unittest under tests/test_*.py with a stdlib
runner at tests/run_tests.py (unit | integration | path).
- .githooks/commit-msg ported to Python; same Conventional Commits rules.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
46 lines
1.3 KiB
Python
Executable File
46 lines
1.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Enforce Conventional Commits on the first line of the commit message.
|
|
https://www.conventionalcommits.org/en/v1.0.0/
|
|
|
|
Activate per clone with:
|
|
git config core.hooksPath .githooks
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ALLOWED_PREFIXES = ("Merge ", "Revert ", "fixup! ", "squash! ", "amend! ")
|
|
PATTERN = re.compile(
|
|
r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)"
|
|
r"(\([a-z0-9._-]+\))?!?: .+"
|
|
)
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
if len(argv) < 1:
|
|
print("commit-msg: missing message file path", file=sys.stderr)
|
|
return 1
|
|
|
|
msg_file = Path(argv[0])
|
|
text = msg_file.read_text(encoding="utf-8", errors="replace")
|
|
first_line = text.splitlines()[0] if text else ""
|
|
|
|
if any(first_line.startswith(p) for p in ALLOWED_PREFIXES):
|
|
return 0
|
|
|
|
if not PATTERN.match(first_line):
|
|
sys.stderr.write("commit-msg: aborting — message does not follow Conventional Commits.\n")
|
|
sys.stderr.write(" expected: <type>[(<scope>)][!]: <description>\n")
|
|
sys.stderr.write(" types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert\n")
|
|
sys.stderr.write(f" got: {first_line}\n")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|