From 365c55534d53f511f609047e9d1d4b61b9ccf8bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 4 Nov 2026 07:44:02 +0000 Subject: [PATCH 1/2] chore(template-sync): sync to v1.8.4 --- .github/scripts/README.md | 6 +++++ .github/scripts/ruff.toml | 6 +++-- .github/scripts/test_moderate_apply_result.py | 12 +++++++-- .github/scripts/test_moderate_tools.py | 12 ++++++--- .github/template-sync-manifest.yml | 16 ++++++++++++ .github/template-sync-state.json | 2 +- .pre-commit-config.yaml | 25 +++++++++++++++++++ docs/TEMPLATE.md | 18 +++++++++++-- 8 files changed, 87 insertions(+), 10 deletions(-) diff --git a/.github/scripts/README.md b/.github/scripts/README.md index 6d2d1fa..d4905d6 100644 --- a/.github/scripts/README.md +++ b/.github/scripts/README.md @@ -89,6 +89,12 @@ that label the test fails until you look up the new image's `python3` (actions/runner-images) and update `RUNNER_IMAGE_PYTHON`, `RUNNER_PYTHON` and `ruff.toml` together. +- Formatting: base has no ruff dependency of its own, so `ruff-check`/`ruff-format` + from `astral-sh/ruff-pre-commit` in `../../.pre-commit-config.yaml`, scoped + to `^\.github/scripts/.*\.py$`, keep these scripts formatted to `ruff.toml`. + Unformatted scripts would otherwise fail every Python instance's own + `ruff format` hook after a sync. Keep that hook's `rev` equal to the ruff + the Python templates pin. - `test_moderate_labels.py` — checks that every `create "" "" ""` line in `../workflows/moderate-setup.yml` has a description of at most 100 characters (GitHub answers HTTP 422 to a longer one, and only diff --git a/.github/scripts/ruff.toml b/.github/scripts/ruff.toml index dc0a302..dfa88ee 100644 --- a/.github/scripts/ruff.toml +++ b/.github/scripts/ruff.toml @@ -23,6 +23,8 @@ line-length = 100 [lint] # Ruff's long-standing defaults, spelled out so a newer ruff that widens -# its default rule set does not start flagging these scripts. Base itself -# does not run ruff; this keeps `ruff check` quiet in every checkout. +# its default rule set does not start flagging these scripts. Base runs +# ruff on this directory only through the ruff-pre-commit hook in +# ../../.pre-commit-config.yaml; this keeps `ruff check` quiet in every +# checkout. select = ["E4", "E7", "E9", "F"] diff --git a/.github/scripts/test_moderate_apply_result.py b/.github/scripts/test_moderate_apply_result.py index 4e8b025..5b34b86 100644 --- a/.github/scripts/test_moderate_apply_result.py +++ b/.github/scripts/test_moderate_apply_result.py @@ -280,7 +280,9 @@ def test_base_sha_must_be_a_full_hex_sha(self) -> None: (self.tmp / "log.txt").unlink(missing_ok=True) proc = self.run_script(meta_extra={"base_sha": bad}) log = self.log() - self.assertFalse([c for c in log if c.startswith(("git fetch", "git am", "gh api"))]) + self.assertFalse( + [c for c in log if c.startswith(("git fetch", "git am", "gh api"))] + ) self.assert_failed_cleanly(proc, log) def test_denied_path_in_the_patch_is_never_pushed(self) -> None: @@ -317,7 +319,13 @@ def test_ordinary_paths_pass(self) -> None: """Source, tests and docs (also next to look-alike names) are accepted.""" diff = "".join( f":100644 100644 a b M\\0{p}\\0" - for p in ("src/a.py", "tests/test_a.py", "docs/Makefile.md", "scripts/other.sh", ".githubx/y") + for p in ( + "src/a.py", + "tests/test_a.py", + "docs/Makefile.md", + "scripts/other.sh", + ".githubx/y", + ) ) proc = self.run_script(run_env={"FAKE_DIFF": diff}) self.assertEqual(proc.returncode, 0, proc.stderr) diff --git a/.github/scripts/test_moderate_tools.py b/.github/scripts/test_moderate_tools.py index 6ed9244..c695e8d 100644 --- a/.github/scripts/test_moderate_tools.py +++ b/.github/scripts/test_moderate_tools.py @@ -270,7 +270,9 @@ def test_key_only_from_the_environment(self) -> None: self.assertIn("KEY:sk-test", lines) self.assertIn("SCRUB:1", lines) self.assertIn("AUTHOR:claude-moderator[bot]", lines) - self.assertEqual(lines[lines.index("ARG:--allowedTools") + 1], "ARG:Read,Grep,Bash(uv run pytest *)") + self.assertEqual( + lines[lines.index("ARG:--allowedTools") + 1], "ARG:Read,Grep,Bash(uv run pytest *)" + ) settings = json.loads(lines[lines.index("ARG:--settings") + 1].removeprefix("ARG:")) self.assertIn("Read(//proc/**)", settings["permissions"]["deny"]) sandbox = settings["sandbox"] @@ -281,14 +283,18 @@ def test_key_only_from_the_environment(self) -> None: self.assertNotIn("enableWeakerNestedSandbox", sandbox) self.assertEqual(sandbox["network"]["allowedDomains"], ["github.com", "api.github.com"]) self.assertIs(sandbox["network"]["strictAllowlist"], True) - self.assertIn({"name": "ANTHROPIC_API_KEY", "mode": "deny"}, sandbox["credentials"]["envVars"]) + self.assertIn( + {"name": "ANTHROPIC_API_KEY", "mode": "deny"}, sandbox["credentials"]["envVars"] + ) self.assertEqual(lines[lines.index("ARG:-p") + 1], "ARG:do the thing") def test_missing_or_bad_domains_fail_before_claude_runs(self) -> None: """No network allow-list, or a malformed host, means claude never starts.""" for domains in ("", ",", 'x.com","evil', "a b"): with self.subTest(domains=domains): - proc = self.launch({"ANTHROPIC_API_KEY": "k", "MODERATION_ALLOWED_DOMAINS": domains}) + proc = self.launch( + {"ANTHROPIC_API_KEY": "k", "MODERATION_ALLOWED_DOMAINS": domains} + ) self.assertEqual(proc.returncode, 1) self.assertFalse(self.out.exists()) diff --git a/.github/template-sync-manifest.yml b/.github/template-sync-manifest.yml index 361a723..f8c7a2f 100644 --- a/.github/template-sync-manifest.yml +++ b/.github/template-sync-manifest.yml @@ -150,6 +150,7 @@ ignore: # Each instance chooses its own license and copyright holder. - LICENSE +<<<<<<< /home/runner/work/template-react/template-react/.github/template-sync-manifest.yml # Derived from package.json, which is merge-tier -- a 3-way merge of a # lock file is unreliable, so an instance regenerates it locally with # `pnpm install` after a sync PR changes package.json. @@ -173,6 +174,18 @@ ignore: # Env-var documentation for the instance's own deployment. - .env.example +======= + # Each instance's README is entirely its own (image, usage, runtime); a + # merge against this template's README conflicted on every sync, so it + # is instance-owned from creation, like the Dockerfile. + - README.md + + # The instance's own image: its Dockerfile (toolchain, build, runtime + # stages) and the image.mk variables that tune the template's Makefile. + # The skeletons shipped here are examples to replace, never synced. + - Dockerfile + - image.mk +>>>>>>> /tmp/template-new/.github/template-sync-manifest.yml # The instance's ZAP baseline rule levels (seeded with a commented header; # an instance adds its own FAIL/IGNORE lines, with a reason each). @@ -203,6 +216,7 @@ ignore: - .github/template-sync-manifest.local.yml merge: +<<<<<<< /home/runner/work/template-react/template-react/.github/template-sync-manifest.yml # Project identity (name/description/version) is interpolated per # instance; everything else in these two files is template-owned. - README.md @@ -213,6 +227,8 @@ merge: # tests); an instance adds its own on top. - image.mk +======= +>>>>>>> /tmp/template-new/.github/template-sync-manifest.yml # Baseline excludes the instance extends with its own entries. - .dockerignore diff --git a/.github/template-sync-state.json b/.github/template-sync-state.json index 96d6131..dcb891e 100644 --- a/.github/template-sync-state.json +++ b/.github/template-sync-state.json @@ -1,4 +1,4 @@ { "template_repo": "craftainer/template-docker", - "synced_tag": "v1.8.3" + "synced_tag": "v1.8.4" } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c4a05f..554642b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,6 +38,9 @@ repos: - id: check-added-large-files stages: [pre-commit, manual] - id: check-merge-conflict + # Without this the hook only runs during an in-progress git merge, so + # conflict markers committed by a template-sync PR were never caught. + args: [--assume-in-merge] stages: [pre-commit, manual] - id: mixed-line-ending stages: [pre-commit, manual] @@ -56,10 +59,32 @@ repos: - id: conventional-pre-commit stages: [commit-msg] +<<<<<<< /home/runner/work/template-react/template-react/.pre-commit-config.yaml # oxlint, prettier, tsc and vitest are already project dev-dependencies # (see package.json) — running them via `pnpm` keeps their pinned # versions as the single source of truth instead of a second, separate # pin per tool in this file. +======= + # Guard for the upstream-owned scripts in .github/scripts/: base ships + # them to every instance, whose own ruff-format hook then checks them + # against .github/scripts/ruff.toml (the closest config wins), so an + # unformatted script here fails every Python instance after a sync. Base + # has no ruff dependency of its own, hence this pinned hook, scoped to + # that directory only. Keep the rev equal to the ruff the Python + # templates pin in pyproject.toml. + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.16.8 + hooks: + - id: ruff-check + name: ruff check (.github/scripts) + files: ^\.github/scripts/.*\.py$ + stages: [pre-commit, manual] + - id: ruff-format + name: ruff format (.github/scripts) + files: ^\.github/scripts/.*\.py$ + stages: [pre-commit, manual] + +>>>>>>> /tmp/template-new/.pre-commit-config.yaml - repo: local hooks: - id: oxlint diff --git a/docs/TEMPLATE.md b/docs/TEMPLATE.md index 75d2928..60223a7 100644 --- a/docs/TEMPLATE.md +++ b/docs/TEMPLATE.md @@ -108,13 +108,20 @@ instance's own toolchain needs. `.pre-commit-config.yaml` defines whitespace/EOF fixers, YAML/TOML/JSON checks, `conventional-pre-commit` (Conventional Commits, enforced at the `commit-msg` stage), the `template-sync-manifest` completeness check, and -the `devcontainer-lock` lockfile-consistency check, and three security -scanners (below). An instance layers +the `devcontainer-lock` lockfile-consistency check, `ruff-check`/`ruff-format` +(`astral-sh/ruff-pre-commit`, scoped to `.github/scripts/*.py` so base can't +ship scripts that fail a Python instance's own ruff hook; keep its `rev` equal +to the ruff the Python templates pin), and three security scanners (below). An instance layers its own language-specific hooks on top (lint, format, type check, test suite, dependency-vulnerability scan, ...), matching the shape `template-fastapi`'s own `.pre-commit-config.yaml` already establishes for `ruff`/`mypy`/`pytest`/`pip-audit`. +`check-merge-conflict` runs with `--assume-in-merge`, so it flags committed +conflict markers (a line starting `<<<<<<< `, `=======` or `>>>>>>> `) in any +file, not just during an in-progress git merge; a template sync PR that +commits a merge-tier conflict is then caught in CI. + ### Security scanners | Hook | Scans | Stage | Network | OWASP (2025) | @@ -882,6 +889,13 @@ the place for their own runtime (FastAPI, axum, React) and their own ADRs. A mid-layer classifies its own tree for its instances in its own `.github/template-sync-manifest.yml`, which is `merge` tier in every instance: base's additions are resolved by hand there. +In `template-docker`, `README.md` is `ignore` tier: each instance's +README is entirely its own, so a merge conflicted on every sync. The +instance owns it from creation, and later changes to the template's +README no longer reach instances by sync. This applies through the +chain: the README of `template-fastapi`, `template-axum` and +`template-react` becomes owned by that app template, and its own +instances follow that template's manifest. An instance lists extra paths of its own under `ignore:` in `.github/template-sync-manifest.local.yml`. Which template an instance follows is recorded in its `.github/template-sync-state.json` From 37ae45d8a144a5b29e6adea99f675e1e5519aa3b Mon Sep 17 00:00:00 2001 From: crazyracer98 <8631139+crazyracer98@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:54:11 +0000 Subject: [PATCH 2/2] fix(template-sync): resolve committed conflict markers, keeping both sides - .pre-commit-config.yaml: keep the new ruff guard for .github/scripts and the pnpm-hooks comment - template-sync-manifest.yml: README.md is instance-owned (ignore) as in template-docker; image.mk stays merge-tier and Dockerfile stays replace-tier (template-react ships its own) --- .github/template-sync-manifest.yml | 14 +------------- .pre-commit-config.yaml | 11 ++++------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/template-sync-manifest.yml b/.github/template-sync-manifest.yml index f8c7a2f..4470a0e 100644 --- a/.github/template-sync-manifest.yml +++ b/.github/template-sync-manifest.yml @@ -150,7 +150,6 @@ ignore: # Each instance chooses its own license and copyright holder. - LICENSE -<<<<<<< /home/runner/work/template-react/template-react/.github/template-sync-manifest.yml # Derived from package.json, which is merge-tier -- a 3-way merge of a # lock file is unreliable, so an instance regenerates it locally with # `pnpm install` after a sync PR changes package.json. @@ -174,19 +173,12 @@ ignore: # Env-var documentation for the instance's own deployment. - .env.example -======= + # Each instance's README is entirely its own (image, usage, runtime); a # merge against this template's README conflicted on every sync, so it # is instance-owned from creation, like the Dockerfile. - README.md - # The instance's own image: its Dockerfile (toolchain, build, runtime - # stages) and the image.mk variables that tune the template's Makefile. - # The skeletons shipped here are examples to replace, never synced. - - Dockerfile - - image.mk ->>>>>>> /tmp/template-new/.github/template-sync-manifest.yml - # The instance's ZAP baseline rule levels (seeded with a commented header; # an instance adds its own FAIL/IGNORE lines, with a reason each). - .zap/rules.tsv @@ -216,10 +208,8 @@ ignore: - .github/template-sync-manifest.local.yml merge: -<<<<<<< /home/runner/work/template-react/template-react/.github/template-sync-manifest.yml # Project identity (name/description/version) is interpolated per # instance; everything else in these two files is template-owned. - - README.md - package.json - pnpm-workspace.yaml @@ -227,8 +217,6 @@ merge: # tests); an instance adds its own on top. - image.mk -======= ->>>>>>> /tmp/template-new/.github/template-sync-manifest.yml # Baseline excludes the instance extends with its own entries. - .dockerignore diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 554642b..428792c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,12 +59,6 @@ repos: - id: conventional-pre-commit stages: [commit-msg] -<<<<<<< /home/runner/work/template-react/template-react/.pre-commit-config.yaml - # oxlint, prettier, tsc and vitest are already project dev-dependencies - # (see package.json) — running them via `pnpm` keeps their pinned - # versions as the single source of truth instead of a second, separate - # pin per tool in this file. -======= # Guard for the upstream-owned scripts in .github/scripts/: base ships # them to every instance, whose own ruff-format hook then checks them # against .github/scripts/ruff.toml (the closest config wins), so an @@ -84,7 +78,10 @@ repos: files: ^\.github/scripts/.*\.py$ stages: [pre-commit, manual] ->>>>>>> /tmp/template-new/.pre-commit-config.yaml + # oxlint, prettier, tsc and vitest are already project dev-dependencies + # (see package.json) — running them via `pnpm` keeps their pinned + # versions as the single source of truth instead of a second, separate + # pin per tool in this file. - repo: local hooks: - id: oxlint