diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index 3cc6afa..98d186d 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -1,10 +1,13 @@
-# Review feature PRs that follow the CodesWhat dev-branch flow. CodeRabbit
-# reviews the default branch without an explicit base-branch entry.
+# Request the first review only after the coordinator admits a stable candidate.
+# Later fix batches need an explicit review request; pushes do not spend capacity.
language: en-US
reviews:
auto_review:
- enabled: true
+ enabled: false
+ auto_incremental_review: false
drafts: false
+ labels:
+ - "review-ready"
base_branches:
- "^dev/.*$"
diff --git a/.github/tests/community_health_contract_test.py b/.github/tests/community_health_contract_test.py
index 69c2020..d62de0f 100644
--- a/.github/tests/community_health_contract_test.py
+++ b/.github/tests/community_health_contract_test.py
@@ -171,11 +171,10 @@ def test_standards_workflow_pins_actions_and_runs_every_contract(self):
for expected in (
"persist-credentials: false",
"community_health_contract_test.py",
- "python3 -m compileall -q .",
- "YAML.parse_file",
- "json.load",
- "markdownlint-cli2@0.23.2",
- "markdownlint-profile.yaml",
+ "python3 .github/tests/tracked_validation_test.py",
+ "python3 scripts/validate_tracked_files.py python",
+ "python3 scripts/validate_tracked_files.py data",
+ "python3 scripts/validate_tracked_files.py markdown",
"actionlint",
"zizmorcore/zizmor-action@",
"github/codeql-action/init@",
diff --git a/.github/tests/tracked_validation_test.py b/.github/tests/tracked_validation_test.py
new file mode 100644
index 0000000..b657ab1
--- /dev/null
+++ b/.github/tests/tracked_validation_test.py
@@ -0,0 +1,102 @@
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+import unittest
+
+
+ROOT = Path(__file__).resolve().parents[2]
+VALIDATOR = ROOT / "scripts/validate_tracked_files.py"
+
+
+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,
+ )
+ self.write(".gitignore", ".planning/\n.claude/\n__pycache__/\n", tracked=True)
+
+ def write(self, name, content, tracked=False):
+ path = self.repo / name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(content)
+ if tracked:
+ subprocess.run(
+ ["git", "add", "--", name], cwd=self.repo, check=True,
+ capture_output=True,
+ )
+
+ def run_validation(self, kind):
+ return subprocess.run(
+ [sys.executable, str(VALIDATOR), kind], cwd=self.repo,
+ capture_output=True, text=True, timeout=60,
+ )
+
+ def assert_valid(self, kind):
+ result = self.run_validation(kind)
+ self.assertEqual(0, result.returncode, result.stdout + result.stderr)
+
+ def assert_invalid(self, kind, diagnostic):
+ result = self.run_validation(kind)
+ self.assertNotEqual(0, result.returncode, result.stdout + result.stderr)
+ self.assertIn(diagnostic, result.stdout + result.stderr)
+
+ def test_ignored_and_untracked_files_are_not_validation_inputs(self):
+ for directory in (".planning", ".claude/worktrees/other", "scratch"):
+ for suffix, content in (
+ ("py", "def broken(:\n"), ("json", "{broken"),
+ ("yml", "broken: ["), ("yaml", "broken: ["),
+ ("md", "no heading\n"),
+ ):
+ self.write(f"{directory}/bad.{suffix}", content)
+ self.write("scripts/space [x]\nname.py", "answer = 42\n", tracked=True)
+ self.write(".github/data [x].json", '{"ok": true}\n', tracked=True)
+ self.write(".github/data.yml", "ok: true\n", tracked=True)
+ self.write("docs/a [x].md", "# Valid\n", tracked=True)
+ for kind in ("python", "data", "markdown"):
+ with self.subTest(kind=kind):
+ self.assert_valid(kind)
+
+ def test_invalid_tracked_python_fails(self):
+ self.write(".github/bad [x].py", "def broken(:\n", tracked=True)
+ self.assert_invalid("python", "SyntaxError")
+
+ def test_invalid_tracked_json_fails(self):
+ self.write(".github/bad [x].json", "{broken", tracked=True)
+ self.assert_invalid("data", "JSONDecodeError")
+
+ def test_invalid_tracked_yaml_fails(self):
+ for suffix in ("yml", "yaml"):
+ with self.subTest(suffix=suffix):
+ name = f".github/bad.{suffix}"
+ self.write(name, "broken: [", tracked=True)
+ self.assert_invalid("data", "Psych::SyntaxError")
+ self.write(name, "ok: true\n", tracked=True)
+
+ def test_invalid_tracked_markdown_paths_are_literal(self):
+ for name in ("docs/bad [x].md", ".github/hidden.md", "#note.md"):
+ with self.subTest(name=name):
+ self.write(name, "no heading\n", tracked=True)
+ self.assert_invalid("markdown", "MD041")
+ self.write(name, "# Valid\n", tracked=True)
+
+ def test_profile_keeps_its_separate_rules(self):
+ self.write("profile/README.md", "
Profile
\n", tracked=True)
+ config = ".github/markdownlint-profile.yaml"
+ self.write(config, (ROOT / config).read_text(), tracked=True)
+ self.assert_valid("markdown")
+ self.write("profile/README.md", "Profile
\n", tracked=True)
+ self.assert_invalid("markdown", "MD009")
+
+ def test_empty_tracked_input_sets_succeed(self):
+ for kind in ("python", "data", "markdown"):
+ with self.subTest(kind=kind):
+ self.assert_valid(kind)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/.github/workflows/standards-validation.yml b/.github/workflows/standards-validation.yml
index 703d696..112817f 100644
--- a/.github/workflows/standards-validation.yml
+++ b/.github/workflows/standards-validation.yml
@@ -41,15 +41,15 @@ jobs:
run: |
python3 .github/tests/community_health_contract_test.py
python3 .github/tests/greptile_summon_contract_test.py
+ python3 .github/tests/tracked_validation_test.py
- name: Compile Python
- run: python3 -m compileall -q .
+ run: python3 scripts/validate_tracked_files.py python
- name: Parse YAML and JSON
run: |
set -euo pipefail
- ruby -e 'require "yaml"; Dir.glob("**/*.{yml,yaml}", File::FNM_DOTMATCH).sort.each { |path| YAML.parse_file(path) }'
- python3 -c 'import json; from pathlib import Path; [json.load(path.open()) for path in Path(".").rglob("*.json")]'
+ python3 scripts/validate_tracked_files.py data
python3 .github/tests/greptile_config_contract_test.py
python3 .github/tests/quality_report_contract_test.py
python3 .github/tests/reusable_ci_contract_test.py
@@ -60,8 +60,7 @@ jobs:
- name: Lint Markdown
run: |
set -euo pipefail
- npx --yes markdownlint-cli2@0.23.2 "**/*.md" "#profile/README.md"
- npx --yes markdownlint-cli2@0.23.2 "profile/README.md" --config .github/markdownlint-profile.yaml
+ python3 scripts/validate_tracked_files.py markdown
- name: Validate workflows with actionlint
run: |
diff --git a/AGENTS.md b/AGENTS.md
index 0d27337..956f396 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -30,9 +30,16 @@ contract tests that pin the shapes other repositories depend on.
Verify promotions with tree equality
(`git diff --quiet origin/main origin/dev/repository-standards`), never
commit ancestry.
-- CodeRabbit auto-reviews PRs against `dev/*` branches only (scoped in
- `.coderabbit.yaml`); summon it explicitly anywhere else and read its
- inline comments before merging.
+- 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.
+ 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.
- Commits are plain Conventional Commits, `(scope): `,
no emoji, no AI attribution trailers.
- Never weaken branch protection or rulesets to land a change.
diff --git a/REPOSITORY_ONBOARDING.md b/REPOSITORY_ONBOARDING.md
index 84db9ce..06068c5 100644
--- a/REPOSITORY_ONBOARDING.md
+++ b/REPOSITORY_ONBOARDING.md
@@ -107,27 +107,67 @@ repositories. Language-specific tests do not.
- [ ] Confirm the Renovate GitHub app can access the repository. App access is
managed at the organization level; do not widen a public-only installation
to private repositories as an onboarding shortcut.
-- [ ] Add a root `.coderabbit.yaml` based on a current CodesWhat product, then
- replace its product-specific tone, ignored paths, and path instructions.
- Keep automatic review enabled. If PRs target a non-default integration
- branch, include that branch under `reviews.auto_review.base_branches` and
- request an explicit CodeRabbit review on the first PR to prove it works.
+- [ ] Where CodeRabbit is adopted, use a root `.coderabbit.yaml` appropriate to
+ the repository. Adopt intentional admission rather than automatically spending
+ review capacity on every in-progress push. Scope opt-in and incremental-review
+ changes through normal review, preserving required review evidence. Verify
+ non-default integration targets and stacked PR behavior before relying on them.
- [ ] Confirm CodeRabbit can access the repository and posts an actual review
comment. A green check without review output is not enough for this test.
- Automated Pro-level reviews are free on public repositories only; on
- private repositories the free plan rate-limits automated reviews, and they
- have not fired for this organization in practice. Organization policy: on a
- private repository skip both CodeRabbit items, use cross-account human
- review, and add the config when the repository goes public.
+ Inspect the effective plan, selected-file limit and remaining capacity; public
+ repository visibility is not a promise of unlimited review. A quota-only green
+ check is not delivered review. Required non-author approval still applies, and
+ multiple accounts operated by one person are not independent human review.
- [ ] Keep `greptile.json` at exactly `{"skipReview": "AUTOMATIC"}`
(contract-tested in this repository) so Greptile never reviews unbidden. A
repository that wants opt-in second opinions adds a label-gated caller
workflow (`.github/workflows/greptile.yml`, firing on the `second-opinion`
label) that calls this repository's `greptile-summon.yml` at a pinned full
commit SHA. Pair the caller with a CodeRabbit `labeling_instructions` entry
- for `second-opinion` and `auto_apply_labels` enabled, so applying the label
- is criteria-driven rather than left to memory. The label is Greptile's only
- trigger; never wire it to review every PR directly.
+ for `second-opinion` without automatic label application. Applying it is an
+ explicit, consequence-based admission decision with available capacity.
+ The label is Greptile's only automatic caller trigger; do not wire it to
+ review every PR directly. Inspect the provider's resolved configuration and
+ delivered review rather than assuming a legacy file or summon proves coverage.
+
+### Capacity and queued development
+
+Public projects use queue-first development, with adoption work recorded in
+their private internal roadmaps. Begin with one newly admitted hosted-review or
+heavy-CI candidate across public projects for a seven-day measured pilot. Finish
+existing in-flight work safely. A named coordinator records the candidate's
+owner, head/base, dependencies, local verification, services, review-pass count,
+blocked reason and next eligible time before new expensive work starts.
+
+Keep one active change per repo and at most one small dependent successor local.
+Pilot two-layer stacks for genuine dependencies; verify trunk, bot and merge
+compatibility. Draft and stacked PRs can still run CI and deploys. Keep diffs
+small, aiming around 25 changed files and within the actual reviewer limit;
+batching many unrelated items into one oversized PR is not admission control.
+
+Freeze pushes during review, then submit one batch of fixes per pass. Prioritize
+finishing admitted work over opening another PR. Keep the three-pass cap, all
+required checks and valid approvals. Record cooldowns and wait for recovery
+without repeated review summons, workflow reruns or indefinite polling. Review
+capacity after the pilot using actual service pools before expanding concurrency.
+
+Admit one new deployment-producing web candidate per shared deployment pool
+initially. Pilot ordinary dependency arrivals at one concurrent PR and one new
+PR/hour per repo, with staggered schedules or dashboard admission and urgent
+vulnerability handling retained. Retain the monthly Qlty reporting policy below.
+Provider settings, bot controls and required CI changes need scoped reviewed
+rollouts; documentation alone does not configure them. Metadata-only projects
+record applicable controls without installing irrelevant tools or paid services.
+
+This shared repository pilots the controls in its own configuration, without
+changing the live Renovate preset for consumers. The coordinator approves an
+ordinary dependency update in the dashboard only after admission; the local
+wrapper permits one ordinary PR/branch and one new PR per hour. Urgent
+vulnerability handling remains separate. After admitting a stable review
+candidate, apply `review-ready` for its first CodeRabbit review. Later fix
+batches use an explicit review request; pushes do not automatically start
+another review. Verify the provider's resolved configuration and delivered head
+after landing these settings before claiming the pilot is operational.
### Local gates
@@ -294,41 +334,55 @@ Add these only when the behavior exists:
### Qlty
-Qlty appears in three places. The committed configuration and the repository-run
-gates are the alignment surface; the Qlty Cloud GitHub App is not.
-
-- [ ] Commit `.qlty/qlty.toml` (`config_version = "0"`) with the organization
- exclude baseline for generated, minified, and vendored paths. Drydock's file
- is the reference posture and Portwing's is its Go-repo mirror; start from the
- closer of the two. Do not copy the trivy plugin block from either reference:
- trivy is deprecated in favor of Grype and its removal from both files is
- tracked (drydock#753, portwing#135). The trufflehog plugin stays. The same
- file drives the local CLI and Qlty Cloud, so changes to it are quality-gate
- changes, not formatting.
-- [ ] Add the repository's Qlty gate script (`scripts/qlty-check-gate.sh all`)
- to `pre-push`, fail-fast like every other local gate. An advisory smells
- gate may run alongside it (Drydock pattern) but must not mask the gating
- script's failure.
-- [ ] Gate Qlty in CI with the repository-run check, which needs no Qlty Cloud
- account. Go repositories call `go-ci.yml` with `run-qlty` enabled,
- `qlty-egress-policy: block`, and the proven endpoint allowlist — copy the
- allowlist from Portwing's `ci-verify.yml` rather than re-deriving it, and do
- not widen it. Node repositories run the SHA-pinned
- `qltysh/qlty-action/install` plus the same gate script inside their own
- workflow (Drydock pattern).
-
-The Qlty Cloud GitHub App's statuses (`qlty check`, `qlty coverage`,
-`qlty coverage diff`) currently error organization-wide with out-of-minutes
-billing failures. Treat them as non-gating: never add them to required status
-checks, and do not chase these failures on PRs — the repository-run gate above
-is the enforced check. Coverage reporting is Codecov's job (decided
-2026-08-16): upload coverage to Codecov and carry its badge in the README.
-The Qlty Cloud App and its maintainability badge stay installed, but its
-coverage upload is not the coverage system and its checks stay non-required.
-Qlty Cloud enrollment and usage data are not an organization-wide
-prerequisite; do not make a Cloud upload or check required
-until the repository is enrolled and the check has proved stable. Local Qlty
-checks may still be stricter than this baseline.
+Public projects run routine quality checks in their own CI and useful local
+hooks. Keep Qlty CLI where it aggregates useful checks; effective native tools
+are also valid. Qlty Cloud supplies periodic reporting for existing dashboards
+and README metrics. A Cloud subscription is not needed for standalone CLI checks.
+
+- [ ] Record adoption work or a concrete applicability decision in the project's
+ private internal roadmap, with an owner, next action and checkable completion
+ conditions. Metadata-only repositories do not need a new scanner or dashboard
+ without a useful signal.
+- [ ] Where Qlty CLI is used, commit `.qlty/qlty.toml` with exclusions for
+ generated, minified and vendored paths. Review changes to shared CLI/Cloud
+ configuration as quality-gate changes. Preserve useful existing checks rather
+ than duplicating them through another tool.
+- [ ] Run the repository's quality gate in CI and useful local hooks. Advisory
+ duplication/complexity reports must not mask a gate failure. The shared
+ `go-ci.yml` supports `run-qlty`, which invokes `scripts/ci/go-qlty.sh` in the
+ caller repository. Keep the existing input default and check name; consumers
+ adopt changes through deliberate full-SHA pin updates. Node repositories can
+ use the SHA-pinned `qltysh/qlty-action/install` and their own gate script.
+- [ ] For existing Qlty Cloud maintainability reporting, set default-branch
+ Analysis Frequency to `1 month` in Project Settings > General. A manual scan
+ for a release or important change is acceptable within the usage budget. The
+ README maintainability grade represents the last completed scan and may be up
+ to a month behind the current default branch.
+- [ ] Avoid redundant hosted PR analysis after equivalent repository-owned CI
+ gates are verified. Verify the control stops analysis compute; turning off
+ comments or commit statuses alone does not establish that scans stop. Do not
+ weaken branch protection. Any replacement for a required gate goes through a
+ reviewed repository change and must be verified on the intended commit first.
+- [ ] Preserve useful coverage reporting, enforcement and badges, including
+ Codecov where already adopted. Each Qlty coverage upload costs one standard
+ analysis minute, including each separately uploaded server-merge part. Count
+ all workspace scans, uploads, runner multipliers and retries before claiming
+ the allowance is sufficient. Do not silently reduce PR coverage enforcement
+ to save reporting minutes.
+- [ ] Verify provider-side commit identity, actual analysis/coverage content and
+ badge freshness after changing settings. Classify quota failures separately
+ from real analysis/upload failures; a successful upload step or status alone
+ does not establish delivery or a completed scan.
+
+This operating policy retains useful Qlty CLI and periodic Cloud reporting. It
+does not introduce Sonar, require a paid upgrade, or automatically enroll every
+repository in a hosted service. The reusable workflows keep their existing
+interfaces and opt-in defaults; a caller's workflow does not configure Qlty
+Cloud's service-side scheduling.
+
+See Qlty's [analysis frequency](https://docs.qlty.sh/cloud/analysis-frequency),
+[analysis-minute accounting](https://docs.qlty.sh/cloud/billing/analysis-minutes),
+and [CLI versus Cloud](https://docs.qlty.sh/cli-vs-cloud) documentation.
## 5. Add delivery controls only for shipped artifacts
diff --git a/renovate.json b/renovate.json
index 6bba2f8..3e6d303 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,4 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": ["local>CodesWhat/.github:renovate-config"]
+ "extends": ["local>CodesWhat/.github:renovate-config"],
+ "dependencyDashboardApproval": true,
+ "prConcurrentLimit": 1,
+ "prHourlyLimit": 1,
+ "branchConcurrentLimit": 1
}
diff --git a/scripts/generate_profile_svg.py b/scripts/generate_profile_svg.py
index 436b7c6..06b4025 100644
--- a/scripts/generate_profile_svg.py
+++ b/scripts/generate_profile_svg.py
@@ -4,7 +4,6 @@
Creates dark_mode.svg and light_mode.svg.
"""
-import re
from pathlib import Path
@@ -92,7 +91,7 @@ def generate_svg(mode="dark"):
]
# Wordmark auto-scales to fit the canvas, capped at the logo font size
- wordmark_max_len = max(len(l.rstrip()) for l in wordmark_art)
+ wordmark_max_len = max(len(line.rstrip()) for line in wordmark_art)
wordmark_font_size = round(min(font_size, (width - 40) / (0.6 * wordmark_max_len)), 1)
wordmark_char_width = wordmark_font_size * 0.6
wordmark_line_height = round(wordmark_font_size * 1.2)
@@ -145,7 +144,7 @@ def generate_svg(mode="dark"):
# Art lines have ragged lengths; center the block as a whole off the
# widest line so every line shares one left edge
- art_max_len = max(len(l) for l in logo_art)
+ art_max_len = max(len(line) for line in logo_art)
art_start_x = (width - art_max_len * char_width) / 2
solid_chars = set('█▓▌▐▄▀')
diff --git a/scripts/validate.sh b/scripts/validate.sh
index e6d9504..8f5f86f 100755
--- a/scripts/validate.sh
+++ b/scripts/validate.sh
@@ -17,17 +17,18 @@ python3 .github/tests/greptile_config_contract_test.py
python3 .github/tests/quality_report_contract_test.py
python3 .github/tests/reusable_ci_contract_test.py
python3 .github/tests/renovate_config_contract_test.py
+python3 .github/tests/starchart_refresh_contract_test.py
+python3 .github/tests/main_is_released_contract_test.py
+python3 .github/tests/tracked_validation_test.py
echo "==> compile python"
-python3 -m compileall -q .
+python3 scripts/validate_tracked_files.py python
echo "==> parse yaml and json"
-ruby -e 'require "yaml"; Dir.glob("**/*.{yml,yaml}", File::FNM_DOTMATCH).sort.each { |path| YAML.parse_file(path) }'
-python3 -c 'import json; from pathlib import Path; [json.load(path.open()) for path in Path(".").rglob("*.json")]'
+python3 scripts/validate_tracked_files.py data
echo "==> markdownlint"
-npx --yes markdownlint-cli2@0.23.2 "**/*.md" "#profile/README.md"
-npx --yes markdownlint-cli2@0.23.2 "profile/README.md" --config .github/markdownlint-profile.yaml
+python3 scripts/validate_tracked_files.py markdown
echo "==> actionlint"
if command -v actionlint >/dev/null 2>&1; then
diff --git a/scripts/validate_tracked_files.py b/scripts/validate_tracked_files.py
new file mode 100644
index 0000000..347903d
--- /dev/null
+++ b/scripts/validate_tracked_files.py
@@ -0,0 +1,59 @@
+import argparse
+import json
+import os
+from pathlib import Path
+import py_compile
+import subprocess
+import sys
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("kind", choices=("python", "data", "markdown"))
+ kind = parser.parse_args().kind
+ root = subprocess.check_output(
+ ["git", "rev-parse", "--show-toplevel"], text=True,
+ ).strip()
+ os.chdir(root)
+ # NUL delimiters preserve filenames containing spaces or newlines.
+ paths = [
+ Path(os.fsdecode(path))
+ for path in subprocess.check_output(["git", "ls-files", "-z"]).split(b"\0")
+ if path
+ ]
+
+ if kind == "python":
+ for path in paths:
+ if path.suffix == ".py":
+ py_compile.compile(str(path), doraise=True)
+ elif kind == "data":
+ for path in paths:
+ if path.suffix == ".json":
+ with path.open() as source:
+ json.load(source)
+ yaml_paths = [str(path) for path in paths if path.suffix in (".yml", ".yaml")]
+ if yaml_paths:
+ subprocess.run(
+ ["ruby", "-e", 'require "yaml"; ARGV.each { |path| YAML.parse_file(path) }',
+ "--", *yaml_paths], check=True,
+ )
+ else:
+ profile = Path("profile/README.md")
+ markdown = [path for path in paths if path.suffix == ".md" and path != profile]
+ command = ["npx", "--yes", "markdownlint-cli2@0.23.2", "--no-globs"]
+ if markdown:
+ subprocess.run(
+ [*command, *[":./" + str(path) for path in markdown]], check=True,
+ )
+ if profile in paths:
+ subprocess.run(
+ [*command, ":./" + str(profile),
+ "--config", ".github/markdownlint-profile.yaml"], check=True,
+ )
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except subprocess.CalledProcessError as error:
+ sys.exit(error.returncode)