feat: add GUIDE.md for GitHub automation and update references in AGENTS.md and README.md

This commit is contained in:
maksyuki committed 2026-07-12 09:08:42 +08:00
1 parent 373a08d458
commit 97c40010cc
5 files changed
+48 -5

No files matched your search

File renamed without changes.
+2
View File
@@ -2,12 +2,14 @@ version: 2
updates: updates:
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
target-branch: dev
schedule: schedule:
interval: weekly interval: weekly
open-pull-requests-limit: 5 open-pull-requests-limit: 5
- package-ecosystem: pip - package-ecosystem: pip
directory: /requirements directory: /requirements
target-branch: dev
schedule: schedule:
interval: monthly interval: monthly
open-pull-requests-limit: 5 open-pull-requests-limit: 5
+1 -1
View File
@@ -31,7 +31,7 @@ and validation boundary.
| Directory | Guide | Role | | Directory | Guide | Role |
| --- | --- | --- | | --- | --- | --- |
| `.github/` | [.github/README.md](.github/README.md) | CI, release automation, reusable actions, and ownership metadata. | | `.github/` | [.github/GUIDE.md](.github/GUIDE.md) | CI, release automation, reusable actions, and ownership metadata. |
| `app/` | [app/README.md](app/README.md) | Firmware applications and integrations. | | `app/` | [app/README.md](app/README.md) | Firmware applications and integrations. |
| `config/` | [config/README.md](config/README.md) | Locked external inputs and checksums. | | `config/` | [config/README.md](config/README.md) | Locked external inputs and checksums. |
| `configs/` | [configs/README.md](configs/README.md) | Reproducible build profiles. | | `configs/` | [configs/README.md](configs/README.md) | Reproducible build profiles. |
+1
View File
@@ -35,6 +35,7 @@ under the [Mulan Permissive Software License, Version 2](LICENSE).
| [`pdk/`](pdk) | PDK setup entry points and technology-specific integration. | | [`pdk/`](pdk) | PDK setup entry points and technology-specific integration. |
| [`syn/`](syn) and [`sta/`](sta) | Synthesis and static timing analysis flows. | | [`syn/`](syn) and [`sta/`](sta) | Synthesis and static timing analysis flows. |
| [`scripts/`](scripts) and [`quality/`](quality) | Build helpers, regression orchestration, checks, warning baselines, and metric policy. | | [`scripts/`](scripts) and [`quality/`](quality) | Build helpers, regression orchestration, checks, warning baselines, and metric policy. |
| [`.github/`](.github) | GitHub automation, Dependabot configuration, and CI/release workflows; see [`GUIDE.md`](.github/GUIDE.md). |
| [`docs/`](docs) | Engineering workflow and release-process documentation. | | [`docs/`](docs) | Engineering workflow and release-process documentation. |
## Supported Configurations ## Supported Configurations
+44 -4
View File
@@ -521,21 +521,61 @@ def test_ci_actions_are_pinned_to_commits() -> None:
assert re.fullmatch(r"[^@]+@[0-9a-f]{40}", value), f"unpinned action in {path}: {value}" assert re.fullmatch(r"[^@]+@[0-9a-f]{40}", value), f"unpinned action in {path}: {value}"
HASH_LOCKED_REQUIREMENT = re.compile(
r"[^\s=]+==[^\s]+(?:\s+--hash=sha256:[0-9a-f]{64})+"
)
def hash_locked_requirement_lines(content: str) -> list[str]:
logical_content = re.sub(r"\\\r?\n[ \t]*", " ", content)
return [
line.strip()
for line in logical_content.splitlines()
if line.strip() and not line.lstrip().startswith(("#", "--"))
]
def test_python_requirements_are_hash_locked() -> None: def test_python_requirements_are_hash_locked() -> None:
for path in sorted((ROOT / "requirements").glob("*.txt")): for path in sorted((ROOT / "requirements").glob("*.txt")):
content = path.read_text(encoding="utf-8") content = path.read_text(encoding="utf-8")
assert "--require-hashes" in content assert "--require-hashes" in content
assert "--only-binary=:all:" in content assert "--only-binary=:all:" in content
requirements = [ requirements = hash_locked_requirement_lines(content)
line for line in content.splitlines() if line and not line.startswith(("#", "--"))
]
assert requirements assert requirements
for requirement in requirements: for requirement in requirements:
assert re.search(r"==[^ ]+ --hash=sha256:[0-9a-f]{64}$", requirement), ( assert HASH_LOCKED_REQUIREMENT.fullmatch(requirement), (
f"unlocked requirement in {path}: {requirement}" f"unlocked requirement in {path}: {requirement}"
) )
def test_python_requirement_hash_validation_accepts_multiple_hashes() -> None:
first_hash = "a" * 64
second_hash = "b" * 64
requirements = hash_locked_requirement_lines(
"--only-binary=:all:\n"
"--require-hashes\n"
"ruff==0.15.21 \\\n"
f" --hash=sha256:{first_hash} \\\n"
f" --hash=sha256:{second_hash}\n"
)
assert len(requirements) == 1
assert HASH_LOCKED_REQUIREMENT.fullmatch(requirements[0])
def test_python_requirement_hash_validation_rejects_unlocked_entries() -> None:
valid_hash = "a" * 64
invalid_requirements = (
"ruff>=0.15.21 --hash=sha256:" + valid_hash,
"ruff==0.15.21",
"ruff==0.15.21 --hash=sha256:" + "a" * 63,
"ruff==0.15.21 --hash=sha256:" + valid_hash + " --extra",
)
for requirement in invalid_requirements:
assert not HASH_LOCKED_REQUIREMENT.fullmatch(requirement)
def test_clean_all_stays_within_repository(tmp_path: Path) -> None: def test_clean_all_stays_within_repository(tmp_path: Path) -> None:
fake_root = tmp_path / "repository" fake_root = tmp_path / "repository"
(fake_root / ".git").mkdir(parents=True) (fake_root / ".git").mkdir(parents=True)