diff --git a/AGENTS.md b/AGENTS.md index 093f01a..0b08e57 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,6 +62,20 @@ Organization documentation rules are checked by `python scripts/check_style.py`, consumer. - Do not commit private or licensed source data. + +- Public material carries no real resource names or accounts: write + `gs:///...` and ``, and use synthetic values in + examples. +- Read bucket names, accounts and hostnames from `vars.*`, the + environment, or protected configuration; do not hard-code them in + source, workflows or documents. +- A hash is not anonymisation: a commit id, digest or truncated + identifier that leads back to the real object does not belong in + public material. +- Judge a change by `git diff` and `git ls-files`; `.gitignore` only + affects untracked files and is not evidence that nothing leaked. + + ## Commits and pull requests Conventional Commits with an English imperative subject of 72 characters or diff --git a/scripts/check_style.py b/scripts/check_style.py index 58db1be..aeaeb5d 100755 --- a/scripts/check_style.py +++ b/scripts/check_style.py @@ -56,6 +56,11 @@ DATE_IN_NAME = re.compile(r"(?" + # A date in the name is a defect when the file describes something that keeps # changing, because the date goes stale while the name stays. It is the # identity of the record when the file is a snapshot of one date: two analyses @@ -355,6 +360,15 @@ def check_agent_files(root: Path, report: Report) -> None: if content != "@AGENTS.md": report.add("agents", "CLAUDE.md must contain the single line @AGENTS.md") + agents = root / "AGENTS.md" + if agents.exists(): + text = agents.read_text(encoding="utf-8", errors="ignore") + if CONTENT_BOUNDARY not in text: + report.add( + "agents", + "AGENTS.md is missing the content-boundary block, see section 5", + ) + def check_language(root: Path, report: Report) -> None: marketing = re.compile( diff --git a/scripts/test_style.py b/scripts/test_style.py index de27825..ab691ec 100644 --- a/scripts/test_style.py +++ b/scripts/test_style.py @@ -112,7 +112,7 @@ def test_placeholder_bucket_is_not_reported(self): self.assertEqual(self.findings(text), []) def test_real_bucket_is_reported(self): - found = self.findings("Default root `gs://research-inputs/research/v2/input/`.\n") + found = self.findings("Default root `gs://acme-prod-inputs/research/v2/input/`.\n") self.assertTrue(any("non-placeholder bucket" in m for m in found)) def test_sha_in_url_is_not_reported(self): @@ -184,7 +184,7 @@ def test_untracked_agents_md_is_reported(self): self.assertTrue(any("not tracked by Git" in m for m in found)) def test_tracked_agents_md_passes(self): - self.write("AGENTS.md", "# rules\n") + self.write("AGENTS.md", "# rules\n\n" + check_style.CONTENT_BOUNDARY + "\n") self.write("CLAUDE.md", "@AGENTS.md\n") self.git("init", "--quiet") self.git("add", "AGENTS.md", "CLAUDE.md") @@ -192,6 +192,20 @@ def test_tracked_agents_md_passes(self): check_style.check_agent_files(self.root, report) self.assertEqual(self.messages(report, "agents"), []) + def test_content_boundary_block_is_required(self): + # A rule that lives only in the guide is read by whoever opens the + # guide. The block is the copy that reaches an agent before it writes, + # so its absence is a finding rather than a preference. + self.write("AGENTS.md", "# rules\n") + self.write("CLAUDE.md", "@AGENTS.md\n") + self.git("init", "--quiet") + self.git("add", "AGENTS.md", "CLAUDE.md") + report = self.report() + check_style.check_agent_files(self.root, report) + self.assertTrue( + any("content-boundary" in m for m in self.messages(report, "agents")) + ) + def test_claude_must_be_the_single_line(self): self.write("AGENTS.md", "# rules\n") self.write("CLAUDE.md", "# rules\n\ncopied body\n")