General-purpose coding policy for Baruch's AI agents
73
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
#!/usr/bin/env python3
"""A CHANGELOG entry the publish step cannot stamp is refused on the PR.
The hazard is a branch cut before an intervening publish: its `### ` block
merges BELOW a version heading added since, the stamp step correctly finds
nothing un-headed, and the work ships filed under an already-published version
(#452). These cases pin the check that catches it while a rebase is still the
fix.
"""
import os as _os
import sys as _sys
_ROOT = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
if _ROOT not in _sys.path:
_sys.path.insert(0, _ROOT)
import importlib.util
import json
import subprocess
import tempfile
import unittest
from pathlib import Path
_SCRIPT = _os.path.join(_ROOT, "check-changelog-placement.py")
_SPEC = importlib.util.spec_from_file_location("check_changelog_placement", _SCRIPT)
# Narrowed for the type checker the same way its sibling suite narrows, since
# `spec_from_file_location` is Optional at the type level.
assert _SPEC and _SPEC.loader, f"cannot load check-changelog-placement.py at {_SCRIPT}"
check = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(check)
HEADED = """# Changelog
## 0.3.9 — 2026-01-02
### Added
- **A published entry.** Already stamped.
"""
STAMPABLE = """# Changelog
### Added
- **A new entry.** Not yet stamped.
## 0.3.9 — 2026-01-02
### Added
- **A published entry.** Already stamped.
"""
MISFILED = """# Changelog
## 0.3.9 — 2026-01-02
### Added
- **A new entry.** Not yet stamped.
### Added
- **A published entry.** Already stamped.
"""
VERDICT_EXIT = {"pass": 0, "nothing_to_measure": 0, "misfiled": 1, "error": 2}
def run_script(cmd, **kwargs):
"""Run the checker and hold it to its stdout contract on every outcome.
Exactly one JSON object on stdout, whose verdict agrees with the exit
code; human diagnostics stay on stderr.
"""
proc = subprocess.run(cmd, **kwargs)
payload = json.loads(proc.stdout)
assert set(payload) == {"verdict", "base", "new_items", "reason"}, payload
assert VERDICT_EXIT[payload["verdict"]] == proc.returncode, (payload, proc.returncode)
proc.payload = payload
return proc
TWO_VERSIONS = """# Changelog
## 0.3.9 — 2026-01-02
### Added
- **A second entry.** Published with it.
## 0.3.8 — 2026-01-01
### Added
- **An older entry.** Published first.
"""
# The #581 shape: two releases' items under one heading.
SHARED_HEADING = """# Changelog
## 0.3.9 — 2026-01-02
### Fixed
- **The later release's entry.** Published as 0.3.10.
- **This release's entry.** Published as 0.3.9.
"""
REPAIRED = """# Changelog
## 0.3.10 — 2026-01-02
### Fixed
- **The later release's entry.** Published as 0.3.10.
## 0.3.9 — 2026-01-02
### Fixed
- **This release's entry.** Published as 0.3.9.
"""
class ParkedIdentityTest(unittest.TestCase):
def test_an_entry_above_the_first_heading_is_not_parked(self):
# The published entry below the heading is parked, as it should be;
# the new one above it is not, which is what makes it stampable.
parked = check.parked(STAMPABLE)
self.assertEqual(len(parked), 1)
self.assertIn("A published entry.", parked[0])
self.assertNotIn("A new entry.", "\n".join(parked))
def test_an_entry_under_a_version_heading_is_parked(self):
self.assertEqual(len(check.parked(MISFILED)), 2)
def test_a_changelog_with_no_version_heading_yet_parks_nothing(self):
self.assertEqual(check.parked("# Changelog\n\n### Added\n"), [])
def test_a_moved_block_is_known_to_the_base(self):
self.assertEqual(check.newly_parked(HEADED, HEADED), [])
def test_a_new_block_parked_under_a_heading_is_flagged(self):
flagged = check.newly_parked(HEADED, MISFILED)
self.assertEqual(len(flagged), 1)
self.assertIn("A new entry.", flagged[0])
def test_a_swap_across_headings_that_keeps_the_count_is_still_flagged(self):
# The count rule this replaced was satisfied by adding one misfiled
# item while dropping another parked one. Dropped under one heading
# and added under another is no in-place edit: nothing under the new
# item's heading went away for it to pair with.
swapped = TWO_VERSIONS.replace(
"- **A second entry.** Published with it.\n",
"- **A second entry.** Published with it.\n\n"
"- **A new entry.** Not yet stamped.\n").replace(
"\n- **An older entry.** Published first.\n", "")
self.assertEqual(len(check.parked(swapped)), len(check.parked(TWO_VERSIONS)))
self.assertEqual(len(check.newly_parked(TWO_VERSIONS, swapped)), 1)
def test_a_heading_only_repair_parks_nothing_new(self):
# #581: 0.3.291 published with no heading, its entry filed under
# 0.3.290. The repair adds `## 0.3.291` and a `### Fixed` above
# content the base already carries; only structure changes.
self.assertEqual(check.newly_parked(SHARED_HEADING, REPAIRED), [])
def test_a_declared_in_place_edit_is_not_new(self):
# #503: rewording a published entry replaces the item's text under
# the same heading, and a `Changelog-Edit:` trailer declares it.
edited = HEADED.replace("Already stamped.", "Reworded in place.")
self.assertEqual(check.newly_parked(HEADED, edited, {"0.3.9"}), [])
def test_an_undeclared_same_heading_replacement_is_flagged(self):
# Deleting a published item and adding unrelated work under the same
# heading reads exactly like a reword. Without the declaration it is
# new content parked where the stamp cannot reach.
replaced = HEADED.replace(
"- **A published entry.** Already stamped.\n",
"- **A new entry.** Not yet stamped.\n")
flagged = check.newly_parked(HEADED, replaced)
self.assertEqual(len(flagged), 1)
self.assertIn("A new entry.", flagged[0])
def test_a_declaration_for_another_version_does_not_cover_an_edit(self):
edited = HEADED.replace("Already stamped.", "Reworded in place.")
self.assertEqual(len(check.newly_parked(HEADED, edited, {"0.3.8"})), 1)
def test_an_edit_answers_one_removed_item_only(self):
# One rewritten item pairs with one removed item; a second new item
# under the same heading has nothing left to pair with.
edited = HEADED.replace(
"- **A published entry.** Already stamped.\n",
"- **A published entry.** Reworded in place.\n\n"
"- **A new entry.** Not yet stamped.\n")
flagged = check.newly_parked(HEADED, edited, {"0.3.9"})
self.assertEqual(len(flagged), 1)
def test_an_edit_moved_to_another_heading_is_flagged(self):
# An edit pairs only under the heading the base item sat under.
moved = TWO_VERSIONS.replace(
"- **A second entry.** Published with it.\n\n", "").replace(
"- **An older entry.** Published first.\n",
"- **An older entry.** Published first.\n\n"
"- **A second entry.** Reworded and moved.\n")
self.assertEqual(
len(check.newly_parked(TWO_VERSIONS, moved, {"0.3.8", "0.3.9"})), 1)
def test_a_column_zero_paragraph_is_its_own_item(self):
# Older entries are prose paragraphs, not bullets.
text = ("# Changelog\n\n## 0.3.9 — 2026-01-02\n\n### Added\n\n"
"First paragraph.\n\nSecond paragraph.\n")
self.assertEqual(check.parked(text), ["First paragraph.", "Second paragraph."])
class _RepoCase(unittest.TestCase):
"""A real repository whose `main` carries HEADED."""
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.changelog = self.root / "CHANGELOG.md"
self.git("init", "-q", "-b", "main")
self.git("config", "user.email", "test@example.com")
self.git("config", "user.name", "Test")
self.changelog.write_text(HEADED, encoding="utf-8")
self.git("add", "CHANGELOG.md")
self.git("commit", "-q", "-m", "base")
def git(self, *args):
subprocess.run(["git", *args], cwd=self.root, check=True,
capture_output=True, text=True)
def run_check(self):
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--base", "main"],
cwd=self.root, capture_output=True, text=True)
return proc.returncode, proc.stderr
def rebase_on(self, text):
"""Replace the base commit's changelog with `text`."""
self.changelog.write_text(text, encoding="utf-8")
self.git("commit", "-q", "-am", "base changelog")
def commit_changelog(self, text, message="entry"):
self.git("checkout", "-q", "-b", "work")
self.changelog.write_text(text, encoding="utf-8")
self.git("add", "CHANGELOG.md")
self.git("commit", "-q", "-m", message)
class RepositoryTest(_RepoCase):
"""End to end over a real repository, so the diff half is exercised too."""
def test_a_stampable_entry_passes(self):
self.commit_changelog(STAMPABLE)
code, err = self.run_check()
self.assertEqual(code, 0, err)
def test_an_entry_merged_under_a_heading_is_refused(self):
self.commit_changelog(MISFILED)
code, err = self.run_check()
self.assertEqual(code, 1, err)
self.assertIn("are new since", err)
self.assertIn("Rebase onto main", err)
def test_a_swap_that_keeps_the_count_is_refused(self):
# Adds a misfiled item under one heading and drops a parked one from
# another: the net count is unchanged, and identity still catches it.
self.rebase_on(TWO_VERSIONS)
swapped = TWO_VERSIONS.replace(
"- **A second entry.** Published with it.\n",
"- **A second entry.** Published with it.\n\n"
"- **A new entry.** Not yet stamped.\n").replace(
"\n- **An older entry.** Published first.\n", "")
self.commit_changelog(swapped)
code, err = self.run_check()
self.assertEqual(code, 1, err)
self.assertIn("are new since", err)
def test_a_heading_only_repair_is_allowed(self):
# #581: the repair inserts `## 0.3.10` and `### Fixed` above an item
# the base files under 0.3.9. The old block-identity check read the
# two split blocks as new content and refused it.
self.rebase_on(SHARED_HEADING)
self.commit_changelog(REPAIRED)
code, err = self.run_check()
self.assertEqual(code, 0, err)
def test_a_declared_in_place_edit_of_a_published_entry_is_allowed(self):
# #503: rewording a published entry under its own heading, declared
# by a trailer on a commit in the range.
self.commit_changelog(
HEADED.replace("Already stamped.", "Reworded in place."),
"Reword the entry\n\nChangelog-Edit: 0.3.9\n")
code, err = self.run_check()
self.assertEqual(code, 0, err)
def test_an_undeclared_edit_is_refused_and_names_the_trailer(self):
self.commit_changelog(HEADED.replace("Already stamped.", "Reworded in place."))
code, err = self.run_check()
self.assertEqual(code, 1, err)
self.assertIn("Changelog-Edit: <version>", err)
def test_an_edit_does_not_carry_a_new_item_through(self):
edited = HEADED.replace(
"- **A published entry.** Already stamped.\n",
"- **A published entry.** Reworded in place.\n\n"
"- **A new entry.** Not yet stamped.\n")
self.commit_changelog(edited, "Reword\n\nChangelog-Edit: 0.3.9\n")
code, err = self.run_check()
self.assertEqual(code, 1, err)
self.assertIn("A new entry.", err)
def test_moving_an_entry_between_headings_is_allowed(self):
# An archive repair -- filing a past entry under the version that
# published it -- appears in the diff as an addition below a heading,
# exactly like the hazard. Block-content identity tells them apart: the
# moved block is text the base already carries (#452).
moved = HEADED.replace(
"## 0.3.9 — 2026-01-02\n\n### Added\n\n- **A published entry.** Already stamped.\n",
"## 0.3.10 — 2026-01-03\n\n### Added\n\n- **A published entry.** Already stamped.\n")
self.commit_changelog(moved)
code, err = self.run_check()
self.assertEqual(code, 0, err)
def test_a_second_copy_of_a_parked_entry_is_a_new_parked_block(self):
# coding-policy#457: `set()` dropped multiplicity, so both copies were
# members of the one-element set and a genuinely added duplicate went
# through. Occurrences are consumed one for one.
duplicated = HEADED.replace(
"### Added\n\n- **A published entry.** Already stamped.\n",
"### Added\n\n- **A published entry.** Already stamped.\n"
"\n### Added\n\n- **A published entry.** Already stamped.\n")
self.commit_changelog(duplicated)
code, err = self.run_check()
self.assertEqual(code, 1, err)
self.assertIn("are new since", err)
def test_a_branch_touching_no_entries_passes(self):
self.git("checkout", "-q", "-b", "work")
(self.root / "other.txt").write_text("x", encoding="utf-8")
self.git("add", "other.txt")
self.git("commit", "-q", "-m", "unrelated")
code, err = self.run_check()
self.assertEqual(code, 0, err)
def test_absent_git_is_a_tool_error_not_a_verdict(self):
# `subprocess.run` raises FileNotFoundError, and an uncaught exception
# exits 1 — the misfiling verdict. A missing tool is the absence of an
# answer, so it must exit 2.
self.commit_changelog(MISFILED)
env = dict(_os.environ, PATH=str(self.root / "no-tools"))
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--base", "main"],
cwd=self.root, capture_output=True, text=True, env=env)
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("cannot run `git`", proc.stderr)
def test_an_undecodable_changelog_is_a_tool_error_not_a_verdict(self):
# UnicodeDecodeError is not an OSError; letting it escape exits 1, the
# misfiling verdict, for a file the tool simply cannot read.
self.commit_changelog(MISFILED)
self.changelog.write_bytes(b"# Changelog\n\n\xff\xfe not utf-8\n")
code, err = self.run_check()
self.assertEqual(code, 2, err)
self.assertIn("cannot decode", err)
def commit_undecodable_path(self, author="Test"):
"""Commit a file whose path bytes are not UTF-8; return its sha.
Staged straight into the index so no filesystem has to accept the
name. `diff-tree -z` then emits those bytes raw.
"""
blob = subprocess.run(["git", "hash-object", "-w", "--stdin"],
cwd=self.root, input=b"x", check=True,
capture_output=True).stdout.strip()
subprocess.run(["git", "update-index", "--add", "--cacheinfo",
b"100644," + blob + b",bad-\xff-name"],
cwd=self.root, check=True, capture_output=True)
self.git("-c", "user.name=" + author, "commit", "-q", "-m", "odd path")
return subprocess.run(["git", "rev-parse", "HEAD"], cwd=self.root,
check=True, capture_output=True,
text=True).stdout.strip()
def test_checked_turns_undecodable_output_into_a_tool_error(self):
sha = self.commit_undecodable_path()
cwd = _os.getcwd()
_os.chdir(self.root)
self.addCleanup(_os.chdir, cwd)
with self.assertRaises(RuntimeError) as caught:
check.checked("diff-tree", "-z", "--no-commit-id", "--name-only",
"-r", "--root", sha)
self.assertIn("not UTF-8", str(caught.exception))
def test_undecodable_git_output_is_a_tool_error_not_a_verdict(self):
# A bot commit whose path is not UTF-8 reaches last_publish() through
# checked(). An uncaught UnicodeDecodeError exits 1, the misfiling
# verdict, for output the tool cannot read.
self.commit_undecodable_path(author="github-actions[bot]")
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("not UTF-8", proc.stderr)
self.assertEqual(proc.payload["verdict"], "error")
def test_an_unknown_base_is_a_tool_error_not_a_verdict(self):
self.commit_changelog(STAMPABLE)
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--base", "no-such-ref"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 2)
self.assertIn("failed", proc.stderr)
class PublishModeTest(_RepoCase):
"""`--since-last-publish`: the stamp step's check before it publishes.
#581's merge published 0.3.291 while its entry sat under 0.3.290 and
nothing sat above the first `## ` heading, so the stamp found nothing to
head and the version shipped unheaded. The baseline is the last publish
bookkeeping commit, so a stopped publish stays stopped.
"""
def setUp(self):
super().setUp()
self.manifest = self.root / ".tessl-plugin" / "plugin.json"
self.manifest.parent.mkdir()
self.manifest.write_text('{"version": "0.3.8"}\n', encoding="utf-8")
self.git("add", ".tessl-plugin/plugin.json")
self.git("commit", "-q", "-m", "manifest")
self.bump("0.3.9")
def bump(self, version):
"""The publish pipeline's version-bump commit, authored by the bot."""
self.manifest.write_text('{"version": "%s"}\n' % version, encoding="utf-8")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-am",
"Bump to " + version)
def merge(self, text, message="merge"):
self.changelog.write_text(text, encoding="utf-8")
self.git("commit", "-q", "-am", message)
def run_publish(self):
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish"],
cwd=self.root, capture_output=True, text=True)
self.last = proc.payload
return proc.returncode, proc.stderr
def test_a_merge_parking_its_entry_under_a_published_heading_stops_the_publish(self):
# The #581 shape: the merged item lands inside the block the previous
# stamp headed, so nothing sits above the first `## ` heading.
self.merge(HEADED.replace(
"### Added\n\n",
"### Added\n\n- **A new entry.** Not yet stamped.\n\n"))
code, err = self.run_publish()
self.assertEqual(code, 1, err)
self.assertIn("The publish is stopped", err)
self.assertEqual(self.last["new_items"],
["- **A new entry.** Not yet stamped."])
def test_a_stampable_merge_publishes(self):
self.merge(STAMPABLE)
code, err = self.run_publish()
self.assertEqual(code, 0, err)
def test_a_later_push_cannot_carry_a_stopped_item_through(self):
# The stopped publish wrote no bookkeeping commit, so the next push
# (or a manual dispatch) is measured from the last real publish.
self.merge(MISFILED)
(self.root / "other.txt").write_text("x", encoding="utf-8")
self.git("add", "other.txt")
self.git("commit", "-q", "-m", "unrelated")
code, err = self.run_publish()
self.assertEqual(code, 1, err)
self.assertIn("A new entry.", err)
def test_a_publish_after_the_last_bump_has_nothing_new(self):
# A manual dispatch right after a publish: HEAD is the bump commit.
code, err = self.run_publish()
self.assertEqual(code, 0, err)
def test_a_bot_commit_touching_other_files_is_not_a_baseline(self):
self.merge(MISFILED)
(self.root / "generated.txt").write_text("x", encoding="utf-8")
self.git("add", "generated.txt")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-m", "regen")
code, err = self.run_publish()
self.assertEqual(code, 1, err)
def test_a_declared_edit_publishes(self):
self.merge(HEADED.replace("Already stamped.", "Reworded in place."),
"Reword\n\nChangelog-Edit: 0.3.9\n")
code, err = self.run_publish()
self.assertEqual(code, 0, err)
def test_a_changelog_path_with_spaces_is_still_measured(self):
# `diff-tree --name-only` split on whitespace broke this path into
# pieces, no bookkeeping commit matched, and the gate passed having
# measured nothing.
nested = self.root / "docs" / "My CHANGELOG.md"
nested.parent.mkdir()
nested.write_text(HEADED, encoding="utf-8")
self.git("add", "docs/My CHANGELOG.md")
self.git("commit", "-q", "-m", "move changelog")
nested.write_text(HEADED + "\n", encoding="utf-8")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-am",
"Stamp")
nested.write_text(MISFILED, encoding="utf-8")
self.git("commit", "-q", "-am", "merge")
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish", "--changelog", "docs/My CHANGELOG.md"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 1, proc.stderr)
self.assertEqual(proc.payload["verdict"], "misfiled")
def test_a_non_canonical_changelog_spelling_is_still_measured(self):
# #618: `diff-tree` reports `CHANGELOG.md`, which never equalled
# `./CHANGELOG.md`, so the bot's stamp commit was no baseline and the
# entry it stamped read as new against the older bump.
stamped = TWO_VERSIONS.replace("0.3.9", "0.3.10").replace("0.3.8", "0.3.9")
self.changelog.write_text(stamped, encoding="utf-8")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-am",
"Stamp")
self.merge(stamped.replace(
"## 0.3.10", "### Added\n\n- **A new entry.** Not yet stamped.\n\n## 0.3.10"))
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish", "--changelog", "./CHANGELOG.md"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertEqual(proc.payload["verdict"], "pass")
def test_a_bot_bump_of_a_custom_manifest_is_a_baseline(self):
# #618: the stamp action's `manifest:` input names the file the
# bump commit touches; without it that commit was no baseline, so
# the check measured from an older publish.
custom = self.root / "meta" / "plugin.json"
custom.parent.mkdir()
custom.write_text('{"version": "0.3.9"}\n', encoding="utf-8")
self.git("add", "meta/plugin.json")
self.git("commit", "-q", "-m", "custom manifest")
custom.write_text('{"version": "0.3.10"}\n', encoding="utf-8")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-am",
"Bump to 0.3.10")
bump = subprocess.run(["git", "rev-parse", "HEAD"], cwd=self.root,
check=True, capture_output=True,
text=True).stdout.strip()
self.merge(MISFILED)
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish", "--manifest", "meta/plugin.json"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 1, proc.stderr)
self.assertEqual(proc.payload["base"], bump)
def run_as_action(self, manifest=""):
"""The argv `.github/actions/stamp-changelog/action.yml` passes.
The action forwards its `changelog` and `manifest` inputs verbatim,
an unset `manifest:` as the empty string.
"""
return run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish", "--changelog", "CHANGELOG.md",
"--manifest", manifest],
cwd=self.root, capture_output=True, text=True)
def test_the_action_default_inputs_measure_from_the_last_bump(self):
self.merge(MISFILED)
proc = self.run_as_action()
self.assertEqual(proc.payload["verdict"], "misfiled", proc.stderr)
def test_the_action_forwards_a_custom_manifest_as_a_baseline(self):
# The bot's bump of a custom manifest also stamps an entry; measured
# from the older bump instead, that stamped entry would read as new.
custom = self.root / "meta" / "plugin.json"
custom.parent.mkdir()
custom.write_text('{"version": "0.3.9"}\n', encoding="utf-8")
self.git("add", "meta/plugin.json")
self.git("commit", "-q", "-m", "custom manifest")
stamped = TWO_VERSIONS.replace("0.3.9", "0.3.10").replace("0.3.8", "0.3.9")
self.changelog.write_text(stamped, encoding="utf-8")
custom.write_text('{"version": "0.3.10"}\n', encoding="utf-8")
self.git("-c", "user.name=github-actions[bot]", "commit", "-q", "-am",
"Bump to 0.3.10")
proc = self.run_as_action(manifest="./meta/plugin.json")
self.assertEqual(proc.payload["verdict"], "pass", proc.stderr)
def test_no_publish_in_history_has_nothing_to_measure(self):
fresh = tempfile.TemporaryDirectory()
self.addCleanup(fresh.cleanup)
root = Path(fresh.name)
for args in (("init", "-q", "-b", "main"),
("config", "user.email", "t@example.com"),
("config", "user.name", "Test")):
subprocess.run(["git", *args], cwd=root, check=True, capture_output=True)
(root / "CHANGELOG.md").write_text(MISFILED, encoding="utf-8")
subprocess.run(["git", "add", "CHANGELOG.md"], cwd=root, check=True, capture_output=True)
subprocess.run(["git", "commit", "-q", "-m", "first"], cwd=root, check=True, capture_output=True)
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish"],
cwd=root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("nothing to measure", proc.stderr)
def test_a_shallow_checkout_is_a_tool_error(self):
self.merge(MISFILED)
clone = tempfile.TemporaryDirectory()
self.addCleanup(clone.cleanup)
target = Path(clone.name) / "shallow"
subprocess.run(["git", "clone", "-q", "--depth", "1",
"file://" + str(self.root), str(target)],
check=True, capture_output=True)
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--since-last-publish"],
cwd=target, capture_output=True, text=True)
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("fetch-depth: 0", proc.stderr)
def test_a_changelog_absent_at_the_last_publish_has_nothing_to_measure(self):
self.git("rm", "-q", "CHANGELOG.md")
self.git("commit", "-q", "-m", "drop")
self.bump("0.3.10")
self.changelog.write_text(MISFILED, encoding="utf-8")
self.git("add", "CHANGELOG.md")
self.git("commit", "-q", "-m", "first changelog")
code, err = self.run_publish()
self.assertEqual(code, 0, err)
self.assertIn("does not exist at the last publish", err)
def test_the_two_modes_are_exclusive(self):
proc = run_script(
[_sys.executable, _os.path.join(_ROOT, "check-changelog-placement.py"),
"--base", "main", "--since-last-publish"],
cwd=self.root, capture_output=True, text=True)
self.assertEqual(proc.returncode, 2, proc.stderr)
if __name__ == "__main__":
unittest.main().tessl-plugin
hooks
rules
skills
adopt-fork-pr
herdr-foreman
classify
foreman
references
templates
tests
herdr-standup
migrate-to-plugin
onboard-repo
release
references
tests