Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Git invokes this after the message is available and before creating a commit.
# Staged scope and active-ticket checks remain in the pre-commit hook.
set -euo pipefail

root="$(git rev-parse --show-toplevel)"
policy_runner="$root/.governance/repository_policy.py"
[[ -f "$policy_runner" ]] || policy_runner="$root/scripts/repository_policy.py"
if [[ -f "$policy_runner" ]]; then
profile="$(PYTHONDONTWRITEBYTECODE=1 python3 "$policy_runner" --root "$root" --profile 2>/dev/null || true)"
case "$profile" in
local-audit|main-only-planfile|main-only-files) exit 0 ;;
esac
fi

branch="$(git symbolic-ref --short HEAD 2>/dev/null || true)"
if [[ ! "$branch" =~ ticket[-/]([0-9]{3,}) ]]; then
echo "GOV-AGENT-HOST-001: commit subject cannot bind branch '$branch' to ticket-NNN." >&2
exit 1
fi
ticket="ticket-${BASH_REMATCH[1]}"
if [[ $# != 1 || ! -f "$1" ]]; then
echo "GOV-AGENT-HOST-001: commit message file is unavailable." >&2
exit 1
fi
subject=""
IFS= read -r subject < "$1" || true
if ! printf '%s\n' "$subject" | grep -Eq -- "(^|[^[:alnum:]_-])${ticket}([^[:alnum:]_-]|$)"; then
echo "GOV-AGENT-HOST-001: commit subject must include the branch ticket '$ticket'." >&2
echo " Example: fix(component): repair behavior ($ticket)" >&2
exit 1
fi
21 changes: 21 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ run_commit_guards() {
run_worktree_guard
}

policy_runner="$root/.governance/repository_policy.py"
if [[ -f "$policy_runner" ]]; then
repository_profile="$(PYTHONDONTWRITEBYTECODE=1 python3 "$policy_runner" --root "$root" --profile 2>/dev/null || true)"
if [[ "$repository_profile" == "local-audit" ]]; then
PYTHONDONTWRITEBYTECODE=1 python3 "$policy_runner" --root "$root" --staged >/dev/null || true
exit 0
fi
if [[ "$repository_profile" == "main-only-planfile" || "$repository_profile" == "main-only-files" ]]; then
if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
echo "GOV-AGENT-HOST-001: detached HEAD is not allowed by the main-only delivery profile." >&2
exit 1
fi
if ! PYTHONDONTWRITEBYTECODE=1 python3 "$policy_runner" --root "$root" --staged; then
echo "GOV-AGENT-HOST-002: staged change rejected by the repository delivery profile." >&2
exit 1
fi
run_commit_guards
exit 0
fi
fi

if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
echo "GOV-AGENT-HOST-001: detached HEAD is not bound to ticket-NNN." >&2
exit 1
Expand Down
28 changes: 22 additions & 6 deletions .github/workflows/new-project-governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,36 @@ jobs:
set -euo pipefail
command -v python3 >/dev/null
python3 -c 'import sys; assert sys.version_info >= (3, 11), sys.version'
- name: Install wellman governance runtime
shell: bash
run: |
set -euo pipefail
standard_version="$(python3 -c 'import json; print(json.load(open(".governance/manifest.json", encoding="utf-8"))["standard"]["version"])')"
runtime_ref="v${standard_version}"
python3 -m pip install --disable-pip-version-check "git+https://github.com/wellmanifest/new-project.git@${runtime_ref}#subdirectory=packages/wellman"
wellman --version
- name: Run the managed governance gate
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
# CI has no local clone hook; checks bound to a developer clone are
# skipped for the ci actor, exactly as for governance_check.py below.
range_arguments=()
if [[ -n "${BASE_SHA:-}" || -n "${HEAD_SHA:-}" ]]; then
if [[ -z "${BASE_SHA:-}" || -z "${HEAD_SHA:-}" ]]; then
echo "Both BASE_SHA and HEAD_SHA are required for PR validation." >&2
exit 1
fi
range_arguments=(--base "$BASE_SHA" --head "$HEAD_SHA")
fi
wellman check --root . --actor ci --json "${range_arguments[@]}"
# The adopter's standard-adoption.json mode decides: "enforce" fails on
# missing packs, "audit" reports them without failing the gate.
python3 .governance/standard_pack_check.py --root . --format json
arguments=(--root . --manifest .governance/manifest.json
--lock .governance/manifest.lock.json
--stack-profiles .governance/stack-profiles.json --actor ci)
# A pull request is validated as a range; a scheduled or dispatched run
# has no range, so it validates the repository as it stands.
if [[ -n "${BASE_SHA:-}" && -n "${HEAD_SHA:-}" ]]; then
arguments+=(--base "$BASE_SHA" --head "$HEAD_SHA")
fi
python3 .governance/governance_check.py "${arguments[@]}"
python3 .governance/governance_check.py "${arguments[@]}" "${range_arguments[@]}"
1 change: 1 addition & 0 deletions .governance/agent-hosts.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"hook": {
"path": ".githooks/pre-commit",
"hooksPathConfig": ".githooks",
"additionalHooks": [".githooks/commit-msg"],
"runtimeFiles": [
".governance/worktree_guard.py",
".governance/worktree_overlap_check.py",
Expand Down
14 changes: 9 additions & 5 deletions .governance/agent-hosts.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
"additionalProperties": false,
"required": ["path", "hooksPathConfig", "runtimeFiles"],
"properties": {
"path": { "type": "string", "pattern": "^[^/][^\\\\]*$" },
"path": { "type": "string", "pattern": "^(?!.*(?:^|/)\\.\\.(?:/|$))[^/\\\\:\\x00-\\x1f\\x7f]+(?:/[^/\\\\:\\x00-\\x1f\\x7f]+)*$" },
"hooksPathConfig": { "type": "string", "minLength": 1 },
"additionalHooks": {
"type": "array", "uniqueItems": true,
"items": { "type": "string", "enum": [".githooks/commit-msg", ".githooks/prepare-commit-msg"] }
},
"runtimeFiles": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[^/][^\\\\]*$" }
"items": { "type": "string", "pattern": "^(?!.*(?:^|/)\\.\\.(?:/|$))[^/\\\\:\\x00-\\x1f\\x7f]+(?:/[^/\\\\:\\x00-\\x1f\\x7f]+)*$" }
}
}
},
Expand All @@ -33,7 +37,7 @@
"required": ["id", "file", "readers"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"file": { "type": "string", "pattern": "^[^/][^\\\\]*$" },
"file": { "type": "string", "pattern": "^(?!.*(?:^|/)\\.\\.(?:/|$))[^/\\\\:\\x00-\\x1f\\x7f]+(?:/[^/\\\\:\\x00-\\x1f\\x7f]+)*$" },
"readers": {
"type": "array",
"minItems": 1,
Expand All @@ -59,7 +63,7 @@
"required": ["id", "path"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"path": { "type": "string", "pattern": "^[^/][^\\\\]*$" }
"path": { "type": "string", "pattern": "^(?!.*(?:^|/)\\.\\.(?:/|$))[^/\\\\:\\x00-\\x1f\\x7f]+(?:/[^/\\\\:\\x00-\\x1f\\x7f]+)*$" }
}
}
},
Expand All @@ -74,7 +78,7 @@
"properties": {
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"repository": { "type": "string", "pattern": "^wellmanifest/[a-z0-9.-]+$" },
"path": { "type": "string", "pattern": "^[^/][^\\\\]*$" },
"path": { "type": "string", "pattern": "^(?!.*(?:^|/)\\.\\.(?:/|$))[^/\\\\:\\x00-\\x1f\\x7f]+(?:/[^/\\\\:\\x00-\\x1f\\x7f]+)*$" },
"url": { "type": "string", "pattern": "^https://github\\.com/wellmanifest/[a-z0-9.-]+/blob/main/.+$" }
}
}
Expand Down
Loading
Loading