mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-27 11:22:03 +00:00
ci: refactor paths & clones
ci: centralize path-gating behind single orchestrator + all-checks-pass gate Replace the scattered per-workflow detect-changes pattern with a single ci.yml orchestrator that runs the classifier once, then conditionally calls sub-workflows via workflow_call based on lane outputs. A final all-checks-pass job (if: always()) aggregates all results so branch protection only needs to require one check. Changes: - New .github/workflows/ci.yml orchestrator (detect + conditional calls + all-checks-pass gate) - Extend classify_changes.py with scan/deps/mcp_catalog lanes, absorbing supply-chain-audit's internal changes job - Update detect-changes/action.yml to expose the new lane outputs - Convert all 10 PR-gated sub-workflows to workflow_call-only triggers, removing their push/pull_request triggers and per-step detect-changes guards (gating now happens at the orchestrator level) - lint.yml + supply-chain-audit.yml receive event_name as a workflow_call input to replace github.event_name (which is "workflow_call" inside called workflows) - supply-chain-audit.yml: remove internal changes job + *-gate jobs (orchestrator handles gating, booleans arrive as inputs) - contributor-check.yml: remove internal filter step - Update test_classify_changes.py for 6-lane output + new supply-chain test cases
This commit is contained in:
parent
56b4ef74a6
commit
05c896cf52
14 changed files with 315 additions and 333 deletions
|
|
@ -6,9 +6,18 @@ booleans (one per lane) to ``$GITHUB_OUTPUT`` and stdout. The
|
|||
``detect-changes`` composite action consumes them so steps gate on
|
||||
``if: steps.changes.outputs.<lane> == 'true'``.
|
||||
|
||||
Lanes: ``python`` (pytest / ruff / ty / footguns), ``frontend`` (TS typecheck
|
||||
matrix + desktop build), ``site`` (Docusaurus + generated skill docs). Docker
|
||||
is not a lane — it builds on push-to-main and release only, never per-PR.
|
||||
Lanes:
|
||||
|
||||
* ``python`` — pytest / ruff / ty / footguns.
|
||||
* ``docker_meta`` — Dockerfiles etc.
|
||||
* ``frontend`` — TS typecheck matrix + desktop build.
|
||||
* ``site`` — Docusaurus + generated skill docs.
|
||||
* ``scan`` — supply-chain scan (Python files, .pth, setup hooks).
|
||||
* ``deps`` — pyproject.toml dependency bounds check.
|
||||
* ``mcp_catalog`` — bundled MCP catalog / installer review.
|
||||
|
||||
Docker is not a lane — it builds on push-to-main and release only,
|
||||
never per-PR.
|
||||
|
||||
Contract — *fail open, never closed*. We may run a lane we didn't need, but
|
||||
must never skip one a change could break:
|
||||
|
|
@ -27,10 +36,18 @@ import sys
|
|||
|
||||
_FRONTEND = ("ui-tui/", "web/", "apps/") # TS typecheck-matrix packages
|
||||
_ROOT_NPM = {"package.json", "package-lock.json"} # shifts every package's tree
|
||||
_DOCKER_META = ("docker/", ".hadolint.yml", "Dockerfile") # docker setup
|
||||
_SITE = ("website/", "skills/", "optional-skills/") # docs site + skill pages
|
||||
# Prose/frontend trees that can't touch Python. skills/ is excluded on purpose.
|
||||
_PY_SKIP = ("docs/", "website/") + _FRONTEND
|
||||
|
||||
# Supply-chain scan: files that can execute code at install/import time.
|
||||
_SCAN_EXTS = (".py", ".pth")
|
||||
_SCAN_FILES = {"setup.cfg", "pyproject.toml"}
|
||||
|
||||
# MCP catalog files that require explicit security review.
|
||||
_MCP_CATALOG_PATHS = ("optional-mcps/",)
|
||||
_MCP_CATALOG_FILES = {"hermes_cli/mcp_catalog.py"}
|
||||
|
||||
def _is_docs(p: str) -> bool:
|
||||
if p.startswith(("skills/", "optional-skills/")):
|
||||
|
|
@ -39,18 +56,32 @@ def _is_docs(p: str) -> bool:
|
|||
|
||||
|
||||
def _py_irrelevant(p: str) -> bool:
|
||||
return _is_docs(p) or p in _ROOT_NPM or p.startswith(_PY_SKIP)
|
||||
return _is_docs(p) or p in _ROOT_NPM or p.startswith(_PY_SKIP) or p.startswith(_DOCKER_META)
|
||||
|
||||
|
||||
def _is_scan(p: str) -> bool:
|
||||
return p.endswith(_SCAN_EXTS) or p in _SCAN_FILES
|
||||
|
||||
|
||||
def _is_mcp_catalog(p: str) -> bool:
|
||||
return p.startswith(_MCP_CATALOG_PATHS) or p in _MCP_CATALOG_FILES
|
||||
|
||||
|
||||
def classify(files: list[str]) -> dict[str, bool]:
|
||||
"""Map changed paths to ``{lane: should_run}``."""
|
||||
files = [f.strip() for f in files if f.strip()]
|
||||
if not files or any(f.startswith(".github/") for f in files):
|
||||
return dict.fromkeys(("python", "frontend", "site"), True)
|
||||
return dict.fromkeys(
|
||||
("python", "docker_meta", "frontend", "site", "scan", "deps", "mcp_catalog"), True
|
||||
)
|
||||
return {
|
||||
"python": any(not _py_irrelevant(f) for f in files),
|
||||
"docker_meta": any(f.startswith(_DOCKER_META) for f in files),
|
||||
"frontend": any(f.startswith(_FRONTEND) or f in _ROOT_NPM for f in files),
|
||||
"site": any(f.startswith(_SITE) for f in files),
|
||||
"scan": any(_is_scan(f) for f in files),
|
||||
"deps": any(f == "pyproject.toml" for f in files),
|
||||
"mcp_catalog": any(_is_mcp_catalog(f) for f in files),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue