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
11 changes: 11 additions & 0 deletions .github/tests/community_health_contract_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ def test_standards_workflow_pins_actions_and_runs_every_contract(self):
):
self.assertIn(expected, workflow)

def test_local_validation_runs_every_ci_contract_suite(self):
pattern = r"(?m)^[ \t]*python3[ \t]+\.github/tests/([A-Za-z0-9_]+_test\.py)[ \t]*$"
ci = set(re.findall(
pattern, self.read_text(".github/workflows/standards-validation.yml"),
))
local = set(re.findall(pattern, self.read_text("scripts/validate.sh")))
present = {path.name for path in (ROOT / ".github/tests").glob("*_test.py")}

self.assertEqual(ci, local)
self.assertEqual(present, ci)

def test_zizmor_action_owns_its_supported_cli_version(self):
workflow = self.read_text(".github/workflows/standards-validation.yml")
zizmor_step = workflow.split(
Expand Down
25 changes: 21 additions & 4 deletions .github/tests/tracked_validation_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
import os
import subprocess
import sys
import tempfile
Expand All @@ -9,14 +10,21 @@
VALIDATOR = ROOT / "scripts/validate_tracked_files.py"


def git_env():
names = subprocess.check_output(
["git", "rev-parse", "--local-env-vars"], text=True,
).split()
return {key: value for key, value in os.environ.items() if key not in names}


class TrackedValidationTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="tracked-validation-")
self.addCleanup(self.temp.cleanup)
self.repo = Path(self.temp.name)
subprocess.run(
["git", "init", "--quiet", str(self.repo)], check=True,
capture_output=True,
capture_output=True, env=git_env(),
)
self.write(".gitignore", ".planning/\n.claude/\n__pycache__/\n", tracked=True)

Expand All @@ -27,13 +35,13 @@ def write(self, name, content, tracked=False):
if tracked:
subprocess.run(
["git", "add", "--", name], cwd=self.repo, check=True,
capture_output=True,
capture_output=True, env=git_env(),
)

def run_validation(self, kind):
def run_validation(self, kind, env=None):
return subprocess.run(
[sys.executable, str(VALIDATOR), kind], cwd=self.repo,
capture_output=True, text=True, timeout=60,
capture_output=True, text=True, timeout=60, env=env,
)

def assert_valid(self, kind):
Expand Down Expand Up @@ -92,6 +100,15 @@ def test_profile_keeps_its_separate_rules(self):
self.write("profile/README.md", "<div>Profile</div> \n", tracked=True)
self.assert_invalid("markdown", "MD009")

def test_repo_local_git_environment_is_ignored(self):
self.write(".github/bad.json", "{broken", tracked=True)
env = {**os.environ, "GIT_INDEX_FILE": str(self.repo / "bogus-index")}
result = self.run_validation("data", env=env)
self.assertNotEqual(0, result.returncode, result.stdout + result.stderr)
self.assertIn("JSONDecodeError", result.stdout + result.stderr)
self.write(".github/bad.json", '{"ok": true}\n', tracked=True)
self.assertEqual(0, self.run_validation("data", env=env).returncode)

def test_empty_tracked_input_sets_succeed(self):
for kind in ("python", "data", "markdown"):
with self.subTest(kind=kind):
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/main-is-released.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ name: Main Is Released
# permissions: {}
# jobs:
# released:
# permissions:
# contents: read # A reusable workflow cannot raise its caller's grant.
# uses: CodesWhat/.github/.github/workflows/main-is-released.yml@<full SHA>
#
# Continuously deployed repositories that never tag (a website, a meta repo)
Expand Down
21 changes: 13 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,26 @@ contract tests that pin the shapes other repositories depend on.
with safe defaults, never repurpose an existing input, and let consumers
adopt by rolling their pin deliberately.
- PRs target `dev/repository-standards`. `main` advances only through
promotion PRs. Reconcile before promoting:
promotion PRs. Before reconciling, account for released content:
`git log --oneline origin/dev/repository-standards..origin/main` should list
only the previous promotion's squash commit. Any other commit is content that
landed on `main` directly; forward-port it to dev through a normal reviewed PR
first, because the `ours` merge keeps dev's tree and silently drops it. Then
reconcile:
`git merge -s ours origin/main -m "chore(sync): reconcile main before promotion"`.
Verify promotions with tree equality
(`git diff --quiet origin/main origin/dev/repository-standards`), never
commit ancestry.
- Admit new hosted review/heavy CI work through the shared capacity roster.
Keep preparation local while queued. After admission, add `review-ready` for
the first CodeRabbit review; request subsequent fix-batch reviews explicitly.
- There is no shared admission queue; pace hosted review and heavy CI with
the maintainer directly. Add `review-ready` for a PR's first CodeRabbit
review and request subsequent fix-batch reviews explicitly.
Freeze pushes while review runs, read its output and verify the covered head.
`.coderabbit.yaml` keeps default/dev targets but disables automatic review on
ordinary pushes. Required review and protection remain effective.
- Approve ordinary dependency updates in the Renovate dashboard only after
admission. This repo's wrapper limits ordinary PRs/branches to one at a time
and one new PR per hour. The shared preset is unchanged; urgent vulnerability
updates keep their separate handling.
- Approve ordinary dependency updates in the Renovate dashboard one at a time.
This repo's wrapper targets `dev/repository-standards` and limits ordinary
PRs/branches to one at a time and one new PR per hour. The shared preset is
unchanged; urgent vulnerability updates keep their separate handling.
- Commits are plain Conventional Commits, `<type>(scope): <description>`,
no emoji, no AI attribution trailers.
- Never weaken branch protection or rulesets to land a change.
Expand Down
Loading
Loading