Repository: exercism/python
Pull request #2733 (2021-11-08 07:43:30)

[New Concept Documents] : comparisons concept #2456
This PR adds documentation for the Comparisons concept on the python track.

The following files are updated
 - ./meta/config.json
 - introduction.md
 - about.md
 - links.json



github-actions[bot]: Hi & Welcome! 👋🏽&nbsp;👋

**Thank you** for contributing to `exercism/python` 💛&nbsp;💙 -- we really appreciate it! 🌟 🌈


<br>

This is an automated [🤖&nbsp;🤖 &nbsp;] comment for the  **`maintainers`** of this repository, notifying them of your contribution.  🎉
&nbsp;  Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
You can safely ignore the **`maintainers`**  section below.

<br>
<details open>
  <summary>⚠️ &nbsp;<b>Please be aware</b>&nbsp;⚠️ </summary>

  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_This repo does not generally accept Pull Requests unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_

  &nbsp;&nbsp;&nbsp;&nbsp; 1️⃣ &nbsp;&nbsp;&nbsp; Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise,
  &nbsp;&nbsp;&nbsp;&nbsp; 2️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Medium changes that have been agreed/discussed via a filed issue,
  &nbsp;&nbsp;&nbsp;&nbsp; 3️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Contributions from our [help wanted][help-wanted] issue list,
  &nbsp;&nbsp;&nbsp;&nbsp; 4️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Larger (_previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors.

  &nbsp;&nbsp;&nbsp;&nbsp;Pull Requests not in these categories **will be closed**.  😞

</details>
<br>


---

#### **`💙  It looks like you are changing/adding files in a Concept Exercise! 💙 `**

<br>
<details>
  <summary>‼️&nbsp;&nbsp;<b><em>Did You...</em></b></summary>
<br>
<table>
<td>
<br>

 - [ ]  &nbsp;Update & rebase your branch with any (recent) upstream changes.
 - [ ]  &nbsp;Spell and grammar check all prose changes.
 - [ ]  &nbsp;Run [Prettier](https://prettier.io/) on all markdown and JSON files.
     - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
 - [ ]  &nbsp;Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
 - [ ] &nbsp; Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
 - [ ]  &nbsp;Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
 &nbsp;against their associated test files.
 - [ ]  &nbsp;Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
     - Check that tests **fail** properly, as well as succeed.
     &nbsp;(_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
 - [ ]  &nbsp;Double-check all files for proper EOL.
 - [ ]  &nbsp;[Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
 - [ ]  &nbsp;[Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
     -  Run the generated test file result against its `example.py`.
 - [ ]  &nbsp;Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.

<br>
</td>
</table>
<br>
</details>

<details>
<summary>✅️&nbsp;&nbsp;<b><em>Have You Checked:</em></b></summary>
<br>
<table>
<td>

.
_**Are there any additional changes you need to make?**_
Please make sure all associated files are present and consistent with your changes!

[Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)

- [ ] `.docs/hints.md`
- [ ] `.docs/instructions.md`
- [ ] `.docs/introduction.md`
- [ ] `.meta/config.json`
- [ ] `.meta/design.md`
- [ ] `.meta/exemplar.py` (_exemplar solution_)
- [ ] `<exercise-slug>_test.py` (_test file_)
- [ ] `<exercise-slug>.py` (_stub file_)
- [ ] `concepts/../introduction.md`
- [ ] `concepts/../about.md`
- [ ] `concepts/../links.json`
- [ ] `concepts/../.meta/config.json`

</td>
</table>
<br>
</details>



<h2>🛠️ &nbsp;&nbsp; Maintainers &nbsp;&nbsp;</h2>
<h3> Please take note 📒  of the following sections/review items 👀   ✨ </h3>
<br>
<br>
<details>
<summary> 🌈  &nbsp;Acknowledgements and Reputation</summary>
<br>
<table>
<td>

- ❓ Does this PR need to receive a label with a [`reputation modifier`](https://github.com/exercism/docs/blob/main/using/product/reputation.md#creating-a-pull-request)?
     -  `medium` is awarded by default.
- ❓ Does this contributor need to be added to the **exercise** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#file-metaconfigjson)?
- ❓ Does this contributor need to be added to the **concept** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/concepts.md#file-metaconfigjson)?
- ❓ Is there an associated issue or issues that should be linked to this PR?

</td>
</table>
</details>
<details>
<summary>💫 &nbsp;General Code Quality</summary>
<ul>
<table>
<td>
  <h4>Verify:</h4>

  -  [ ]  &nbsp;The branch was updated & rebased with any (recent) upstream changes.
  -  [ ]  &nbsp;All prose was checked for spelling and grammar.
  -  [ ]  &nbsp;Files are formatted via [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) & conform to our [coding standards](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#coding-standards)
  -  [ ]  &nbsp;Files pass [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) & [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc).
  -  [ ]  &nbsp;Changed `example.py`/`exemplar.py` files still pass their associated **test files**.
  -  [ ]  &nbsp;Changed _**test files**_ still work with associated `example.py`/`exemplar.py` files.
      - Check that tests **fail** properly, as well as succeed.
             (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
  -  [ ]  &nbsp;All files have proper EOL.
  -  [ ]  &nbsp;If a `JinJa2` template was modified/created, was the test file [regenerated](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates)?
      - Does the regenerated test file successfully test the exercises `example.py` file?
  -  [ ]  &nbsp;The branch passes all CI checks & [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint).

<br>
</td>
</table>
</ul>
<br>
</details>
<details>
<summary>🌿 &nbsp;Changes to Concept Exercises</summary>
<br>

  -   ❓ Are all required files still up-to-date & configured correctly for this change?_
  -   ❓ Does `<exercise>/.meta/design.md` need to be updated with new implementation/design decisions
  -   ❓ Do these changes require follow-on/supporting changes to related **concept** documents?

<ul>
<details>
<summary><em><b> ✨ &nbsp;Where applicable, check the following&nbsp;✨  </b></em></summary>
<br>
<ul>
<table>
<td>

(_as a reminder: [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)_)
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're [formatted correctly](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#Test-File_style) for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> **valid UUID4**
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: PyLint linting (**_except for test files_**)
  - [ ]   All files with text:  Spell check & grammar review.

</td>
</table>
</ul>
</details>
</ul>
</details>

<details>
<summary>🚀&nbsp;  Changes to Practice Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Practice Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md)?_
- [ ] `.docs/instructions.md`(**required**)
   - Was this file updated and regenerated properly?
- [ ] `.docs/introduction.md`(_optional_)
- [ ] `.docs/introduction.append.md`(_optional_)
- [ ] `.docs/instructions.append.md` (_optional_)
   - Are any additional instructions needed/provided?
   &nbsp;(_e.g. error handling or information on classes_)
- [ ] `.docs/hints.md`(_optional_)
   - Was this file regenerated properly?
- [ ] `.meta/config.json` (**required**)
- [ ] `.meta/example.py` (**required**)
   - Does this pass all the current tests as written/generated?
- [ ] `.meta/design.md` (_optional_)
- [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
  - Was a test file properly regenerated from this template?
- [ ] `.meta/tests.toml`
   -  Are there additional test cases to include or exclude?
   -  Are there any Python-specific test cases needed for this exercise?
- [ ] `<exercise-slug>_test.py`
   - Does this file need to be regenerated?
   - Does this file correctly test the `example.py` file?
   - Does this file correctly report test failures and messages?
- [ ] `<exercise-slug>.py` (**required**)
   - Does this stub have enough information to get
   &nbsp;the student started coding a valid solution?

</td>
</table>
</ul>
</details>
  <details>
    <summary>🐣 &nbsp;Brand-New Concept Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)?_
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're formatted correctly for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> valid UUID4
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: Flake8 & PyLint linting
  - [ ]   All Code Examples:  proper formatting and fencing.  Verify they run in the REPL
  - [ ]   All files with text:  Spell check & grammar review.

<br>
</td>
</table>
</ul>
</details>
<br>
<h3>Our &nbsp; 💖  &nbsp; for all your review efforts! 🌟 🦄</h3>


<!-- pr-commenter-metadata: any-file-changed,change-concept-exercise -->

Patch:
diff --git a/.github/issue-comment.md b/.github/issue-comment.md
index 7482896f..ef7fe1d7 100644
--- a/.github/issue-comment.md
+++ b/.github/issue-comment.md
@@ -5,31 +5,51 @@ Hi! 👋🏽 👋  Welcome to the Exercism Python Repo!
 Thank you for opening an issue! 🐍 &nbsp;🌈  ✨
 
 <br>
+-   &nbsp;   If you are **requesting support**, we will be along shortly to help. (*generally within* **72 hours,** *often more quickly*).
+-  &nbsp;   **Found a problem** with tests, exercises or something else?? &nbsp;🎉
+&nbsp;&nbsp;&#9702;&nbsp;We'll take a look as soon as we can & identify what work is needed to fix it. *(generally within* **72 hours**).
 
--   &nbsp;   If you are **requesting support**, we will be along shortly to help. (*generally within* **72 hours,** *often more quickly*).  
--  &nbsp;   **Found a problem** with tests, exercises or something else?? &nbsp;🎉  
-&nbsp;&nbsp;We'll take a look as soon as we can & identify what work is needed to fix it. *(generally within* **72 hours**).
-
-​		&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&nbsp;If you'd also like to make a PR to **fix** the issue after discussing it,  
-&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;_We &nbsp;💙 &nbsp;PRs that follow our [Exercism](https://exercism.org/docs/building) & [Track](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) contributing guidelines!_
+​		&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#9702;&nbsp;_If you'd also like to make a PR to **fix** the issue,  please have a quick look at the [Pull Requests][prs] doc._
+&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;_We &nbsp;💙 &nbsp;PRs that follow our [Exercsim][exercism-guidelines] & [Track][track-guidelines] contributing guidelines!_
 
 -  &nbsp; Here because of an obvious (*and* **small** *set of*) spelling, grammar, or punctuation issues with **one** exercise,  
 &nbsp; concept, or Python document?? 🌟  `Please feel free to submit a PR, linking to this issue.` 🎉
 
-​		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ❗ Please **do not**  run checks on the whole repo & submit a bunch of PRs.  
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; This creates longer review cycles & exhausts reviewers energy & time.  
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; It may also conflict with ongoing changes from other contributors.  
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; For anything complicated or ambiguous, **let's discuss things**  --  we will likely welcome a PR from you.
-
-​		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ❗ Please keep in mind that inserting only blank lines, making a closing bracket drop to the next line, changing a  
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;word to a synonym without obvious reason, adding trailing space that's not a[ EOL](https://en.wikipedia.org/wiki/Newline) for the very end of text files,  
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;and other "changes just to change things"  are **not** considered helpful, and will likely be closed by reviewers.
+<ul>
+        <table>
+        <td>
+        <details>
+          <summary>‼️&nbsp;&nbsp;<b><em>Please Do Not</em></b>&nbsp;‼️</summary>
+        <br>
+
+​		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ❗ Run checks on the whole repo & submit a bunch of PRs.  
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; This creates longer review cycles & exhausts reviewers energy & time.  
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; It may also conflict with ongoing changes from other contributors.  
+​		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ❗ Insert only blank lines, make a closing bracket drop to the next line, change a word  
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;to a synonym without obvious reason, or add trailing space that's not an[ EOL][EOL] for the very end of text files.
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ❗ Introduce arbitrary changes "just to change things" .
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _...These sorts of things are **not** considered helpful, and will likely be closed by reviewers._
+
+</details>
+</td>
+</table>
+</ul>
+
+- For anything complicated or ambiguous, **let's discuss things**  --  we will likely welcome a PR from you.
+- _Here to suggest a feature or new exercise??_ **Hooray!** Please keep in mind [_Chesterton's Fence_][chestertons-fence].  
+_Thoughtful suggestions will likely result faster & more enthusiastic responses from maintainers._
 
 <br>
 
-💙 &nbsp;_While you are here..._ If you decide to help out with other [open issues](https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), you have our **gratitude** 🙌 🙌🏽.  
+💛 &nbsp;💙 &nbsp;_While you are here..._ If you decide to help out with other [open issues][open-issues], you have our **gratitude** 🙌 🙌🏽.  
 Anything tagged with `[help wanted]` and without `[Claimed]` is up for grabs.  
 Comment on the issue and we will reserve it for you.  🌈  ✨
 
-_Here to suggest a feature or new exercise??_ **Hooray!** 🎉 &nbsp; Please keep in mind [_Chesterton's Fence_](https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md).  
-_Thoughtful suggestions will likely result faster & more enthusiastic responses from maintainers._
+
+[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
+[EOL]: https://en.wikipedia.org/wiki/Newline
+[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md
+[exercism-guidelines]: https://exercism.org/docs/building
+[open-issues]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
+[track-guidelines]: https://github.com/exercism/python/blob/main/CONTRIBUTING.md
diff --git a/.github/pr-commenter.yml b/.github/pr-commenter.yml
index 6c9a5a07..fa42b8b1 100644
--- a/.github/pr-commenter.yml
+++ b/.github/pr-commenter.yml
@@ -193,14 +193,14 @@ comment:
         <br>
 
         This is an automated [🤖&nbsp;🤖 &nbsp;] comment for the  **`maintainers`** of this repository, notifying them of your contribution.  🎉
-        &nbsp;  Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
+        Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
         You can safely ignore the **`maintainers`**  section below.
 
         <br>
         <details open>
           <summary>⚠️ &nbsp;<b>Please be aware</b>&nbsp;⚠️ </summary>
 
-          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_This repo does not generally accept Pull Requests unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_
+          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_This repo does not generally accept []Pull Requests](https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md) unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_
 
           &nbsp;&nbsp;&nbsp;&nbsp; 1️⃣ &nbsp;&nbsp;&nbsp; Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise,
           &nbsp;&nbsp;&nbsp;&nbsp; 2️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Medium changes that have been agreed/discussed via a filed issue,
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c0ed9605..816a44f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@ Exercises are grouped into **concept** exercises which teach the [Python syllabu
 <img align="left" width="100" height="90" src="https://github.com/exercism/website-icons/blob/main/exercism/logo-big-bordered.png">
 
 🌟🌟&nbsp; If you have not already done so, please take a moment to read our [Code of Conduct][exercism-code-of-conduct].&nbsp;🌟🌟&nbsp;  
-It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].
+It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use], and [Pull Requests][prs].
 
 Some defined roles in our community:  [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers]  **|** [Admins][exercism-admins]
 
@@ -46,6 +46,8 @@ Please&nbsp;📛&nbsp;[ Open an issue ][open-an-issue]📛&nbsp;, and let us kno
 <br>
 
 ## 🚧  **Did you write a patch that fixes a bug?**
+_Before you get started, please review [Pull Requests][prs]._
+
 
  💛 💙&nbsp; **We Warmly Welcome Pull Requests that are:**
 
@@ -62,6 +64,7 @@ When in doubt,&nbsp;📛&nbsp;[ Open an issue ][open-an-issue]📛&nbsp;. We wil
 <p vertical-align="middle"><h2>In General</h2></p>
 <br>
 
+- Please make sure to have a quick read-through of our Exercism [Pull Requests][prs] document before jumping in.&nbsp;😅
 - Maintainers are happy to review your work and help troubleshoot with you.&nbsp;💛&nbsp;💙&nbsp;
   - Requests are reviewed as soon as is practical/possible.
   - (❗&nbsp;) Reviewers may be in a different timezone&nbsp;⌚&nbsp;, or tied up &nbsp;🧶&nbsp; with other tasks.
@@ -84,16 +87,31 @@ When in doubt,&nbsp;📛&nbsp;[ Open an issue ][open-an-issue]📛&nbsp;. We wil
   <details>
     <summary>⚠️&nbsp;&nbsp;<b><em>Pre-Commit Checklist</em></b>&nbsp;⚠️</summary>
     <br>
+    <table>
+    <td>
+    <br>
 
-   1.  &nbsp;Run [`configlet-lint`][configlet-lint] if the track [config.json](config-json) has been modified.
-   2.  &nbsp;Run [Prettier][prettier] on all markdown files.
-       - (_Optionally_) run [yapf][yapf] ([_config file_][.style.yapf]) to help format your code, and give you a head start on making the linters happy.
-   3.  &nbsp;Run [flake8][flake8] ([_config file_][.flake8]) & [pylint][pylint] ([_config file_][pylintrc]) to ensure all Python code files conform to general code style standards.
-   4.  &nbsp;Run `test/check-exercises.py [EXERCISE]` to check if your test changes function correctly.
-   5.  &nbsp;Run the `example.py` or `exemplar.py` file against the exercise test file to ensure that it passes without error.
-   6.  &nbsp;If you modified or created a `hints.md` file for a practice exercise, [regenerate](#generating-practice-exercise-documents) it.
-
-  </details>
+ - [ ]  &nbsp;Update & rebase your branch with any (recent) upstream changes.
+ - [ ]  &nbsp;Spell and grammar check all prose changes.
+ - [ ]  &nbsp;Run [Prettier](https://prettier.io/) on all markdown and JSON files.
+     - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
+ - [ ]  &nbsp;Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
+ - [ ] &nbsp; Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
+ - [ ]  &nbsp;Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
+ &nbsp;against their associated test files.
+ - [ ]  &nbsp;Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or
+         the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
+     - Check that tests **fail** properly, as well as succeed.
+     &nbsp;(_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
+ - [ ]  &nbsp;Double-check all files for proper EOL.
+ - [ ]  &nbsp;[Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
+ - [ ]  &nbsp;[Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
+     -  Run the generated test file result against its `example.py`.
+ - [ ]  &nbsp;Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.
+
+</td>
+</table>
+</details>
 <br>
 
 <img align="left" width="90" height="80" src="https://github.com/exercism/website-icons/blob/main/exercism/logo-big-bordered.png">
@@ -311,7 +329,7 @@ If a practice exercise has an auto-generated `<exercise_slug>_test.py` file, the
 
 _Exercise Structure with Auto-Generated Test Files_
 
-```Bash
+```Graphql
 [<exercise-slug>/
 ├── .docs
 │   └── instructions.md
@@ -379,6 +397,7 @@ configlet generate <path/to/track> --spec-path path/to/problem/specifications
 <br>
 
 
+
 [.flake8]: https://github.com/exercism/python/blob/main/.flake8
 [.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf
 [EOL]: https://en.wikipedia.org/wiki/Newline
@@ -423,6 +442,7 @@ configlet generate <path/to/track> --spec-path path/to/problem/specifications
 [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
 [prettier]: https://prettier.io/
 [problem-specifications]: https://github.com/exercism/problem-specifications
+[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
 [pylint-disable-check]: https://pylint.pycqa.org/en/latest/user_guide/message-control.html#block-disables
 [pylint]: https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html
 [pylintrc]: https://github.com/exercism/python/blob/main/pylintrc
diff --git a/README.md b/README.md
index e35b4814..7b50ab4f 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
 
 Hi. &nbsp;👋🏽 &nbsp;👋 &nbsp;**We are happy you are here.**&nbsp; 🎉&nbsp;🌟
 
-**`exercsim/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
+**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
 This repo holds all the instructions, tests, code, & support files for Python *exercises* currently under development or implemented & available for students.
 
  🌟 &nbsp;&nbsp;Track exercises support Python `3.8`.  
@@ -26,7 +26,7 @@ Exercises are grouped into **concept** exercises which teach the [Python syllabu
 <img align="left" width="100" height="90" src="https://github.com/exercism/website-icons/blob/main/exercism/logo-big-bordered.png">
 
 🌟🌟&nbsp; Please take a moment to read our [Code of Conduct][exercism-code-of-conduct].&nbsp;🌟🌟&nbsp;  
-It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].
+It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member], [The words that we use][the-words-that-we-use], and [Pull Requests][prs].
 
 Some defined roles in our community:  [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers]  **|** [Admins][exercism-admins]
 
@@ -35,7 +35,7 @@ Some defined roles in our community:  [Contributors][exercism-contributors] **|*
 
 We&nbsp;💛&nbsp;💙 &nbsp; Pull Requests. **But our maintainers generally can't accept _unsolicited_ PRs.**  
 Check our [help wanted][open-issues] list or [open an issue ][open-an-issue] for discussion first.  
-We &nbsp;✨💙 &nbsp;💛 &nbsp;💙 ✨&nbsp; PRs that follow our **[Contributing Guidelines][contributing-guidelines]**.
+We &nbsp;✨💙 &nbsp;💛 &nbsp;💙 ✨&nbsp; [PRs][prs] that follow our **[Contributing Guidelines][contributing-guidelines]**.
 
 
 <br>
@@ -72,7 +72,7 @@ Starting with `Python 3.8.6`, examples, recipes, and other code in the Python do
 Some software incorporated into Python is under different licenses. The licenses are listed with code falling under that license. See [Licenses and Acknowledgements for Incorporated Software](https://docs.python.org/3/license.html#otherlicenses) for an incomplete list of these licenses.
 
 
-## License
+## Exercism Python Track License
 This repository uses the [MIT License](/LICENSE).
 
 
@@ -96,6 +96,7 @@ This repository uses the [MIT License](/LICENSE).
 [exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
 [open-an-issue]: https://github.com/exercism/python/issues/new/choose
 [open-issues]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
+[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
 [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
 [psf-license]: https://docs.python.org/3/license.html#psf-license
 [python-syllabus]: https://exercism.org/tracks/python/concepts
diff --git a/concepts/comparisons/.meta/config.json b/concepts/comparisons/.meta/config.json
index 9b9e8da5..67db625c 100644
--- a/concepts/comparisons/.meta/config.json
+++ b/concepts/comparisons/.meta/config.json
@@ -1,5 +1,5 @@
 {
-  "blurb": "TODO: add blurb for this concept",
-  "authors": ["bethanyg", "cmccandless"],
+  "blurb": "Comparison operators evaluate two operand values, returning True or False based on whether the comparison condition is met.",
+  "authors": ["gurupratap-matharu", "bethanyg"],
   "contributors": []
 }
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
index c628150d..3c43a38e 100644
--- a/concepts/comparisons/about.md
+++ b/concepts/comparisons/about.md
@@ -1,2 +1,199 @@
-#TODO: Add about for this concept.
+# About
 
+A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the values of two operands and returns `True` or `False` based on whether the `comparison` condition is met.
+The most common comparison operators are `"<"`, `">"`, `"=="`, `">="`, `"<="`, and `"!="`.
+They all have the same priority (which is higher than that of the Boolean operations).
+
+```python
+>>> 7 > 5
+True
+>>> 99 < 100
+True
+>>> 4 < 4
+False
+>>> 4 <= 4
+True
+>>> 1 >= 1
+True
+>>> 5 == 5
+True
+>>> 6 != 6  # not equal to
+False
+>>> 5 != 6
+True
+>>> not 3 == 3  # interpreted as not(3 == 3)
+False
+```
+
+## Comparison Chaining
+
+Comparisons can be chained arbitrarily, e.g., `x < y <= z` is equivalent to `x < y` `and` `y <= z`, except that `y` is evaluated only once (but in both cases `z` is _not_ evaluated at all when `x < y` is found to be `False`).
+This is also called `short-circuit` evaluation which means the execution is stopped if the truth value of the expression has already been determined.
+Note that the evaluation of expression takes place from left to right.
+In python, short circuiting is supported by various boolean operators, functions and, in this case, comparison chaining.
+
+Also unlike `C`, expressions like `a < b < c` have the interpretation that is conventional in mathematics.
+
+```python
+>>> x = 2
+>>> y = 5
+>>> z = 10
+>>> x < y < z
+True
+>>> x < y > z
+False
+>>> x > y < z
+False
+```
+
+## Comparison between different data types
+
+Since everything in Python is an `object`, things can get interesting when objects of different types are compared.
+For example, the `str` value of a number is considered completely different from the `integer` or `floating-point` value.
+Python makes this distinction because strings are text, while `integers` and `floats` are numeric types.
+
+However, an `integer` **can** be considered equal to a `float`, as they are both numeric types that Python can implicitly convert to compare.
+
+For other numeric types, comparison operators are defined where they "make sense", but throw a `TypeError` if the underlying objects cannot be converted for comparison.
+For more information on the rules that python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
+
+```python
+>>> 17 == '17'
+False
+>>> 17 == 17.0
+True
+>>> 17.0 == 0017.000
+True
+>>> complex(1, 2) == complex(1.0, 2.00)
+True
+```
+
+
+## Value Comparison
+
+The operators `<`, `>`, `==`, `>=`, `<=`, and `!=` compare the _values_ of two different objects.
+The objects do not need to have the same type.
+Remember that in Python every object has a `value` in addition to `type` and `identity`.
+
+### Following are comparison behaviour of most `built-ins` types.
+
+Numbers of built-in _numeric_ types such as `int`, `hex`, `ocal`, `binary`, `float`, `complex` and of the standard library types `fractions.Fraction` and `decimal.Decimal` can be compared within and across their types.
+
+Any ordered comparison of a number to a `NaN` (_not a number_) value is `False`.
+A counter-intuitive implication is that `NaN` never compares equal to `NaN`.
+
+```python
+>>> x = float('NaN')
+>>> x
+nan
+>>> 3 < x
+False
+>>> x < 3
+False
+>>> x == x
+False
+```
+
+Strings (`str`) are compared _lexicographically_ using their individual numerical Unicode code points (_the result of passing each code point in the `str` to the built-in function `ord()`_).
+`str` and `binary` sequences cannot be directly compared.
+
+```python
+>>> 'santa' < 'claus'
+False
+>>> 'Santa' < 'claus'
+True
+>>> ord('s')
+115
+>>> ord('S')
+83
+>>> ord('c')
+99
+>>> # let's try Chinese words
+>>> '你好' < '再见'  # hello < goodbye
+True
+>>> # check ord() of first letters
+>>> ord('你'), ord('再')
+(20320, 20877)
+>>>
+>>>
+>>> # let's try Korean words
+>>> '예쁜' < '아름다운'  # pretty < beautiful
+False
+>>> ord('예'), ord('아')
+(50696, 50500)
+```
+
+Collections like `list`, `set`, `tuple` and `dict` can also be compared -- provided they are of the same `type`, have the same length, and each _**pair**_ of corresponding elements within the collection are comparable.
+
+```python
+>>> [1, 2] == [1, 2]
+True
+>>> (1, 2) == [1, 2]
+False
+>>> [1, 2] < [1, 2, 3]
+True
+>>> # comparison of dicts
+>>> {'name': 'John', 'age': 19} == {'name': 'John', 'age': 18}
+False
+>>> {'name': 'John', 'age': 19} == {'name': 'John', 'age': 19}
+True
+```
+
+## Identity comparisons
+
+The operators `is` and `is not` test for an object's _identity_.
+An object's identity is determined using the `id()` function.
+
+`apple is orange` is `True` if and only if `apple` and `orange` are the same object.
+`apple is not orange` yields the inverse truth value.
+
+```python
+>>> my_fav_numbers = [1, 2, 3]
+>>> your_fav_numbers = my_fav_numbers
+>>> my_fav_numbers == your_fav_numbers
+True
+>>> id(my_fav_numbers)
+4462635008
+>>> id(your_fav_numbers)
+4462635008
+>>> my_fav_numbers is not your_fav_numbers
+False
+```
+
+## Membership test operations
+
+The operators `in` and `not in` test for _membership_.
+`fish in soup` evaluates to `True` if `fish` is a member of `soup`, and evaluates `False` otherwise.
+`fish not in soup` returns the negation, or _opposite of_ `fish in soup`.
+
+For the string and bytes types, `name` in `fullname` is `True` if and only if `name` is a substring of `fullname`.
+
+```python
+lucky_numbers = {11, 22, 33}
+>>> 22 in lucky_numbers
+True
+>>> 44 in lucky_numbers
+False
+>>>
+>>> employee = {
+    'name': 'John Doe',
+    'id': 67826,
+    'age': 33,
+    'title': 'ceo'
+    }
+>>> 'age' in employee
+True
+>>> 33 in employee
+False
+>>> 'lastname' not in employee
+True
+>>>
+>>> name = 'Super Batman'
+>>> 'Bat' in name
+True
+>>> 'Batwoman' in name
+False
+```
+
+[comparisons]: https://docs.python.org/3/library/stdtypes.html?highlight=comparisons#comparisons
+[arithmetic conversions]: https://docs.python.org/3/reference/expressions.html?highlight=number%20conversion#arithmetic-conversions
diff --git a/concepts/comparisons/introduction.md b/concepts/comparisons/introduction.md
index fcde7464..27a105c1 100644
--- a/concepts/comparisons/introduction.md
+++ b/concepts/comparisons/introduction.md
@@ -1,2 +1,23 @@
-#TODO: Add introduction for this concept.
+# Introduction
 
+A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the values of two operands and returns `True` or `False` based on whether the `comparison` condition is met.
+The most common comparison operators are `"<"`, `">"`, `"=="`, `">="`, `"<="`, and `"!="`.
+They all have the same priority (which is higher than that of the Boolean operations)
+
+## Comparison Chaining
+
+Comparisons can be chained arbitrarily, e.g., `x < y <= z` is equivalent to `x < y` `and` `y <= z`, except that `y` is evaluated only once (but in both cases `z` is _not_ evaluated at all when `x < y` is found to be `False`).
+This is also called `short-circuit` evaluation which means the execution is stopped if the truth value of the expression has already been determined.
+Note that the evaluation of expression takes place from left to right.
+In python, short circuiting is supported by various boolean operators, functions and, in this case, comparison chaining.
+
+## Comparison of different data types
+
+Since everything in Python is an `object`, things can get interesting when objects of different types are compared.
+For example, the `str` value of a number is considered completely different from the `integer` or `floating-point` value.
+However, an `integer` **can** be considered equal to a `float`, as they are both numeric types that Python can implicitly convert to compare.
+For other numeric types, comparison operators are defined where they "make sense", but throw a `TypeError` if the underlying objects cannot be converted for comparison.
+For more information on the rules that python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
+
+[comparisons]: https://docs.python.org/3/library/stdtypes.html?
+[arithmetic conversions]: https://docs.python.org/3/reference/expressions.html?highlight=number%20conversion#arithmetic-conversions
diff --git a/concepts/comparisons/links.json b/concepts/comparisons/links.json
index eb5fb7c3..ed61054b 100644
--- a/concepts/comparisons/links.json
+++ b/concepts/comparisons/links.json
@@ -1,18 +1,54 @@
 [
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://docs.python.org/3/reference/expressions.html#comparisons",
+    "description": "Comparisons in Python (Python language reference)"
   },
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://www.tutorialspoint.com/python/python_basic_operators.htm",
+    "description": "Python basic operators on Tutorials Point"
   },
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://data-flair.training/blogs/python-comparison-operators/",
+    "description": "Python comparison operators on Data Flair"
   },
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://www.python.org/dev/peps/pep-0207/",
+    "description": "PEP 207 to allow Operator Overloading for Comparison"
+  },
+  {
+    "url": "https://docs.python.org/3/reference/expressions.html#is-not",
+    "description": "Identity comparisons in Python (Python language reference)"
+  },
+  {
+    "url": "https://docs.python.org/3/library/operator.html",
+    "description": "Operators (Python Docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/library/stdtypes.html#typesnumeric",
+    "description": "Numeric types (Python Docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/library/decimal.html#decimal.Decimal",
+    "description": "Decimal types (Python Docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/library/fractions.html#fractions.Fraction",
+    "description": "Fractions (Python Docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range",
+    "description": "Sequence types (Python Docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/reference/datamodel.html#objects",
+    "description": "Python Object Model (Python docs)"
+  },
+  {
+    "url": "https://docs.python.org/3/reference/datamodel.html#customization",
+    "description": "Basic Customization (Python language reference)"
+  },
+  {
+    "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons",
+    "description": "Value comparisons in Python (Python language reference)"
   }
 ]
diff --git a/exercises/concept/card-games/.docs/instructions.md b/exercises/concept/card-games/.docs/instructions.md
index b8913b97..e5a9c30f 100644
--- a/exercises/concept/card-games/.docs/instructions.md
+++ b/exercises/concept/card-games/.docs/instructions.md
@@ -1,7 +1,5 @@
 # Instructions
 
-## Instructions
-
 Elyse is really looking forward to playing some poker (and other card games) during her upcoming trip to Vegas.
  Being a big fan of "self-tracking" she wants to put together some small functions that will help her with tracking tasks and has asked for your help thinking them through.
 
diff --git a/exercises/concept/currency-exchange/.docs/hints.md b/exercises/concept/currency-exchange/.docs/hints.md
index 4e68c213..95906ef1 100644
--- a/exercises/concept/currency-exchange/.docs/hints.md
+++ b/exercises/concept/currency-exchange/.docs/hints.md
@@ -2,19 +2,19 @@
 
 ## General
 
-- [The Python Numbers Tutorial](https://docs.python.org/3/tutorial/introduction.html#numbers) and [Python numeric types](https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex) can be a great introduction.
+- [The Python Numbers Tutorial][python-numbers-tutorial] and [Python numeric types][python-numeric-types] can be a great introduction.
 
-## 1. Estimating exchangeable value
+## 1. Estimate value after exchange
 
-- You can use the [division operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the value of exchanged currency.
+- You can use the [division operator][division-operator] to get the value of exchanged currency.
 
-## 2. Changes after exchanging
+## 2. Calculate currency left after an exchange
 
-- You can use the [subtraction operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the amount of changes.
+- You can use the [subtraction operator][subtraction-operator] to get the amount of changes.
 
 ## 3. Calculate value of bills
 
-- You can use the [multiplication operator](https://docs.python.org/3/tutorial/introduction.html#numbers) to get the value of bills.
+- You can use the [multiplication operator][multiplication-operator] to get the value of bills.
 
 ## 4. Calculate number of bills
 
@@ -24,8 +24,15 @@
 
   **Note:** The `//` operator also does floor division. But, if the operand has `float`, the result is still `float`.
 
-## 5. Calculate exchangeable value
+## 5. Calculate value after exchange
 
 - You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency.
 - The actual rate needs to be computed. Remember to add exchange rate and exchange fee.
 - You can get exchanged money affected by commission by using divide operation and type casting _int_.
+
+
+[python-numbers-tutorial]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[python-numeric-types]: https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex
+[division-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[subtraction-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
+[multiplication-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
diff --git a/exercises/concept/little-sisters-vocab/.docs/instructions.md b/exercises/concept/little-sisters-vocab/.docs/instructions.md
index 0cfe3d41..64460771 100644
--- a/exercises/concept/little-sisters-vocab/.docs/instructions.md
+++ b/exercises/concept/little-sisters-vocab/.docs/instructions.md
@@ -60,7 +60,7 @@ Implement the `make_word_groups(<vocab_words>)` function that takes a `vocab_wor
 
 `ness` is a common suffix that means _'state of being'_.
  In this activity, your sister needs to find the original root word by removing the `ness` suffix.
-  But of course there are pesky spelling rules: If the root word originally ended in a consonant followed by a 'y', then the 'y' was changed to to 'i'.
+  But of course there are pesky spelling rules: If the root word originally ended in a consonant followed by a 'y', then the 'y' was changed to 'i'.
  Removing 'ness' needs to restore the 'y' in those root words. e.g. `happiness` --> `happi` --> `happy`.
 
 Implement the `remove_suffix_ness(<word>)` function that takes in a word `str`, and returns the root word without the `ness` suffix.
diff --git a/exercises/concept/making-the-grade/.meta/config.json b/exercises/concept/making-the-grade/.meta/config.json
index 52c15858..8e1788ab 100644
--- a/exercises/concept/making-the-grade/.meta/config.json
+++ b/exercises/concept/making-the-grade/.meta/config.json
@@ -1,5 +1,5 @@
 {
-  "blurb": "Learn about loops by grading and organzing your students exam scores.",
+  "blurb": "Learn about loops by grading and organizing your students exam scores.",
   "icon": "grep",
   "authors": ["mohanrajanr", "BethanyG"],
   "contributors": ["pranasziaukas"],
diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/hints.md b/exercises/concept/tisbury-treasure-hunt/.docs/hints.md
index 2d5e7764..94c445aa 100644
--- a/exercises/concept/tisbury-treasure-hunt/.docs/hints.md
+++ b/exercises/concept/tisbury-treasure-hunt/.docs/hints.md
@@ -2,11 +2,11 @@
 
 ## General
 
-- [Tuples](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences) are immutable.
-  [Sequence Types](https://docs.python.org/3/library/stdtypes.html#typesseq) that can contain any data type.
-- Tuples are [iterable](https://docs.python.org/3/glossary.html#term-iterable).
-- Elements within tuples can be accessed via [bracket notation](https://stackoverflow.com/questions/30250282/whats-the-difference-between-the-square-bracket-and-dot-notations-in-python), using a zero-based index.
-- Other [Common Sequence Operations](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations) can also be used when working with tuples.
+- [Tuples][tuples] are immutable.
+  [Sequence Types][sequence types] that can contain any data type.
+- Tuples are [iterable][iterable].
+- Elements within tuples can be accessed via [bracket notation][bracket notation], using a zero-based index.
+- Other [Common Sequence Operations][common sequence operations] can also be used when working with tuples.
 
 ## 1. Extract coordinates
 
@@ -14,23 +14,36 @@
 
 ## 2. Format coordinates
 
-- Check [`class tuple`](https://docs.python.org/3/library/stdtypes.html#tuple) for more details on tuples.
-- Check [`class str`](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str) for more details on strings.
+- Check [`class tuple`][class tuple] for more details on tuples.
+- Check [`class str`][class str] for more details on strings.
 
 ## 3. Match coordinates
 
-- What methods could be used here for for [testing membership](https://docs.python.org/3/reference/expressions.html#membership-test-operations)?.
-- Check [`class tuple`](https://docs.python.org/3/library/stdtypes.html#tuple) for more details on tuples.
+- What methods could be used here for for [testing membership][testing membership]?.
+- Check [`class tuple`][class tuple] for more details on tuples.
 - Could you re-use your `convert_coordinate()` function?
 
 ## 4. Combine matched records
 
-- Remember that tuples support all [common sequence operations](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations).
+- Remember that tuples support all [common sequence operations][common sequence operations].
 - Could you re-use your `compare_records()` function here?
 
-## 5. "Clean up" and format a report of all records
+## 5. "Clean up" & format a report of all records
 
 - Remember: tuples are _immutable_, but the contents can be accessed via _index_ using _bracket notation_.
 - Tuples don't have to use parentheses unless there is _ambiguity_.
-- Python has multiple methods of string formatting. [`str.format()`](https://docs.python.org/3/library/stdtypes.html#str.format) and [`f-strings`](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) are two very common ones.
-- There are multiple textual formatting options available via Pythons [`format specification mini-language`](https://docs.python.org/3/library/string.html#format-specification-mini-language).
+- Python has multiple methods of string formatting. [`str.format()`][str.format] and [`f-strings`][f-strings] are two very common ones.
+- There are multiple textual formatting options available via Pythons [`format specification mini-language`][format specification mini-language].
+
+
+[tuples]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+[sequence types]: https://docs.python.org/3/library/stdtypes.html#typesseq
+[iterable]: https://docs.python.org/3/glossary.html#term-iterable
+[bracket notation]: https://stackoverflow.com/questions/30250282/whats-the-difference-between-the-square-bracket-and-dot-notations-in-python
+[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
+[class tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
+[class str]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str
+[str.format]: https://docs.python.org/3/library/stdtypes.html#str.format
+[f-strings]: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals
+[format specification mini-language]: https://docs.python.org/3/library/string.html#format-specification-mini-language
+[testing membership]: https://docs.python.org/3/reference/expressions.html#membership-test-operations
diff --git a/exercises/practice/alphametics/.meta/example.py b/exercises/practice/alphametics/.meta/example.py
index 6a7a8699..d7261082 100644
--- a/exercises/practice/alphametics/.meta/example.py
+++ b/exercises/practice/alphametics/.meta/example.py
@@ -5,7 +5,7 @@ grouping and counting letters buy digit ranks
 then trace recursively all possible permutations starting from
 the lowest rank and genrating additional permutations for new digits
 at higer ranks as necessary.
-This will allow to avoid unnesessarily large permutations to scan.
+This will allow to avoid unnecessarily large permutations to scan.
 Also leading letters in words will be treated as non-zero digits only
 to reduce the number of permutations
 """
diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md
index 8ec0a7ba..bf49e39d 100644
--- a/exercises/practice/book-store/.docs/instructions.md
+++ b/exercises/practice/book-store/.docs/instructions.md
@@ -43,8 +43,8 @@ This would give a total of:
 
 Resulting in:
 
-- 5 x (8 - 2.00) == 5 x 6.00 == $30.00
-- +3 x (8 - 0.80) == 3 x 7.20 == $21.60
+- 5 * (8 - 2.00) == 5 * 6.00 == $30.00
+- +3 * (8 - 0.80) == 3 * 7.20 == $21.60
 
 For a total of $51.60
 
@@ -60,8 +60,8 @@ This would give a total of:
 
 Resulting in:
 
-- 4 x (8 - 1.60) == 4 x 6.40 == $25.60
-- +4 x (8 - 1.60) == 4 x 6.40 == $25.60
+- 4 * (8 - 1.60) == 4 * 6.40 == $25.60
+- +4 * (8 - 1.60) == 4 * 6.40 == $25.60
 
 For a total of $51.20
 
diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md
index 267ff615..50b19aed 100644
--- a/exercises/practice/complex-numbers/.docs/instructions.md
+++ b/exercises/practice/complex-numbers/.docs/instructions.md
@@ -22,8 +22,8 @@ Dividing a complex number `a + i * b` by another `c + i * d` gives:
 Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`.
 
 Implement the following operations:
- - addition, subtraction, multiplication and division of two complex numbers,
- - conjugate, absolute value, exponent of a given complex number.
 
+- addition, subtraction, multiplication and division of two complex numbers,
+- conjugate, absolute value, exponent of a given complex number.
 
 Assume the programming language you are using does not have an implementation of complex numbers.
diff --git a/exercises/practice/complex-numbers/.meta/example.py b/exercises/practice/complex-numbers/.meta/example.py
index 04113333..4522868f 100644
--- a/exercises/practice/complex-numbers/.meta/example.py
+++ b/exercises/practice/complex-numbers/.meta/example.py
@@ -1,36 +1,87 @@
 import math
 
-
 class ComplexNumber:
-    def __init__(self, real, imaginary):
+    def __init__(self, real=0, imaginary=0):
         self.real = real
         self.imaginary = imaginary
 
     def __eq__(self, other):
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
         return self.real == other.real and self.imaginary == other.imaginary
 
     def __add__(self, other):
-        r = self.real + other.real
-        i = self.imaginary + other.imaginary
-        return ComplexNumber(r, i)
+
+        if not type(other) is ComplexNumber:
+            other = ComplexNumber(other)
+
+        real_part = self.real + other.real
+        imaginary_part = self.imaginary + other.imaginary
+
+        return ComplexNumber(real_part, imaginary_part)
+
+    def __radd__(self, other):
+
+        if not type(other) is ComplexNumber:
+            other = ComplexNumber(other)
+
+        real_part = self.real + other.real
+        imaginary_part = self.imaginary + other.imaginary
+
+        return ComplexNumber(real_part, imaginary_part)
 
     def __mul__(self, other):
-        r = self.real * other.real - self.imaginary * other.imaginary
-        i = self.real * other.imaginary + self.imaginary * other.real
-        return ComplexNumber(r, i)
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+
+        real_part = self.real * other.real - self.imaginary * other.imaginary
+        imaginary_part = self.real * other.imaginary + self.imaginary * other.real
+        return ComplexNumber(real_part, imaginary_part)
+
+    def __rmul__(self, other):
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+
+        real_part = self.real * other.real - self.imaginary * other.imaginary
+        imaginary_part = self.real * other.imaginary + self.imaginary * other.real
+        return ComplexNumber(real_part, imaginary_part)
 
     def __sub__(self, other):
-        r = self.real - other.real
-        i = self.imaginary - other.imaginary
-        return ComplexNumber(r, i)
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+        real_part = self.real - other.real
+        imaginary_part = self.imaginary - other.imaginary
+        return ComplexNumber(real_part, imaginary_part)
+
+    def __rsub__(self, other):
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+
+        real_part = other.real - self.real
+        imaginary_part = other.imaginary - self.imaginary
+        return ComplexNumber(real_part, imaginary_part)
 
     def __truediv__(self, other):
-        d = other.real * other.real + other.imaginary * other.imaginary
-        r = (self.real * other.real + self.imaginary *
-             other.imaginary) / float(d)
-        i = (self.imaginary * other.real - self.real *
-             self.real * other.imaginary) / float(d)
-        return ComplexNumber(r, i)
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+
+        conjugation = other.conjugate()
+        denominator_all = other * conjugation
+        denominator = denominator_all.real
+        numerator = self * conjugation
+
+        return ComplexNumber((numerator.real/denominator), (numerator.imaginary/denominator))
+
+    def __rtruediv__(self, other):
+        if not type(other) == ComplexNumber:
+            other = ComplexNumber(other)
+
+        conjugation = self.conjugate()
+        denominator_all = self * conjugation
+        denominator = float(denominator_all.real)
+        numerator = other * conjugation
+
+        return ComplexNumber((numerator.real/denominator), (numerator.imaginary/denominator))
 
     def __abs__(self):
         square_sum = self.real * self.real + self.imaginary * self.imaginary
@@ -40,6 +91,6 @@ class ComplexNumber:
         return ComplexNumber(self.real, -1 * self.imaginary)
 
     def exp(self):
-        r = math.cos(self.imaginary) * math.exp(self.real)
-        i = math.sin(self.imaginary) * math.exp(self.real)
-        return ComplexNumber(r, i)
+        real_part = math.cos(self.imaginary) * math.exp(self.real)
+        imaginary_part = math.sin(self.imaginary) * math.exp(self.real)
+        return ComplexNumber(real_part, imaginary_part)
diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2
index a5b9db25..86b0d71d 100644
--- a/exercises/practice/complex-numbers/.meta/template.j2
+++ b/exercises/practice/complex-numbers/.meta/template.j2
@@ -1,4 +1,3 @@
-from __future__ import division
 import math
 
 {% extends "master_template.j2" -%}
diff --git a/exercises/practice/diffie-hellman/.meta/template.j2 b/exercises/practice/diffie-hellman/.meta/template.j2
index 6d7f4ec1..23e70792 100644
--- a/exercises/practice/diffie-hellman/.meta/template.j2
+++ b/exercises/practice/diffie-hellman/.meta/template.j2
@@ -33,5 +33,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ expected }}, {{ property }}({% for key in input.keys() %}{{ key | to_snake }},{% endfor %}))
     {%- endif -%}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md
index 8bbbf644..0c716a6e 100644
--- a/exercises/practice/grade-school/.docs/instructions.md
+++ b/exercises/practice/grade-school/.docs/instructions.md
@@ -21,8 +21,8 @@ In the end, you should be able to:
   and Jim in grade 5.
   So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim"
 
-Note that all our students only have one name.  (It's a small town, what
-do you want?)
+Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster.
+In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect.
 
 ## For bonus points
 
diff --git a/exercises/practice/grade-school/.meta/example.py b/exercises/practice/grade-school/.meta/example.py
index a65ca891..83ff1bcb 100644
--- a/exercises/practice/grade-school/.meta/example.py
+++ b/exercises/practice/grade-school/.meta/example.py
@@ -3,17 +3,38 @@ from collections import defaultdict
 
 class School:
     def __init__(self,):
-        self.db = defaultdict(set)
+        self.db = {}
+        self.add = []
+
+    def added(self):
+        result = self.add[:]
+        self.add = []
+        return result
 
     def add_student(self, name, grade):
-        self.db[grade].add(name)
 
-    def roster(self):
-        return [
-            name
-            for grade, names in sorted(self.db.items())
-            for name in sorted(names)
-        ]
+        if not self.db.get(name, 0):
+            self.db[name] = grade
+            self.add.append(True)
+
+        elif self.db[name] > grade:
+            self.db[name] = grade
+
+        else:
+            self.add.append(False)
+
+
+    def roster(self, grade=0):
+        grades_roster = defaultdict(list)
+
+        for key, value in self.db.items():
+            grades_roster[value].append(key)
+
+        if grade:
+            return sorted(grades_roster[grade])
+        else:
+            working_list = (sorted(grades_roster[key]) for key in sorted(grades_roster.keys()))
+            return [element for item in working_list for element in item]
 
     def grade(self, grade_number):
-        return sorted(self.db[grade_number])
+        return sorted(self.roster(grade_number))
diff --git a/exercises/practice/grade-school/.meta/template.j2 b/exercises/practice/grade-school/.meta/template.j2
index bea672a0..8a86c1a8 100644
--- a/exercises/practice/grade-school/.meta/template.j2
+++ b/exercises/practice/grade-school/.meta/template.j2
@@ -1,6 +1,7 @@
 {%- import "generator_macros.j2" as macros with context -%}
 {%- macro test_case( case) -%}
     {%- set input = case["input"] -%}
+    {%- set property = case["property"] -%}
     def test_{{ case["description"] | to_snake }}(self):
         school = School()
         {% for student, grade in input["students"] -%}
@@ -9,7 +10,9 @@
         expected = {{ case["expected"] }}
         {% if "desiredGrade" in input -%}
         self.assertEqual(school.{{ case["property"] | to_snake }}({{ input["desiredGrade"] }}), expected)
-        {%- else -%}
+        {%- elif "add" in property -%}
+        self.assertEqual(school.added(), expected)
+        {% else %}
         self.assertEqual(school.{{ case["property"] | to_snake }}(), expected)
         {%- endif %}
 {% endmacro -%}
@@ -19,5 +22,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases %}
     {{ test_case(case) }}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md
index 56c5696d..12381074 100644
--- a/exercises/practice/hamming/.docs/instructions.md
+++ b/exercises/practice/hamming/.docs/instructions.md
@@ -16,9 +16,8 @@ They have 7 differences, and therefore the Hamming Distance is 7.
 
 The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :)
 
-# Implementation notes
+## Implementation notes
 
 The Hamming distance is only defined for sequences of equal length, so
 an attempt to calculate it between sequences of different lengths should
-not work. The general handling of this situation (e.g., raising an
-exception vs returning a special value) may differ between languages.
+not work.
diff --git a/exercises/practice/high-scores/.meta/example.py b/exercises/practice/high-scores/.meta/example.py
index fd168ffc..48bfd03b 100644
--- a/exercises/practice/high-scores/.meta/example.py
+++ b/exercises/practice/high-scores/.meta/example.py
@@ -8,3 +8,11 @@ def personal_best(scores):
 
 def personal_top_three(scores):
     return sorted(scores, reverse=True)[:3]
+
+
+def latest_after_top_three(scores):
+    return latest(scores)
+
+
+def scores_after_top_three(scores):
+    return scores
diff --git a/exercises/practice/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2
index 4e9ce2eb..596cd34f 100644
--- a/exercises/practice/high-scores/.meta/template.j2
+++ b/exercises/practice/high-scores/.meta/template.j2
@@ -19,7 +19,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endif -%}
     {% endif -%}
     {% endfor %}
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/exercises/practice/high-scores/high_scores.py b/exercises/practice/high-scores/high_scores.py
index 0d24cdb1..327f81cf 100644
--- a/exercises/practice/high-scores/high_scores.py
+++ b/exercises/practice/high-scores/high_scores.py
@@ -8,3 +8,11 @@ def personal_best(scores):
 
 def personal_top_three(scores):
     pass
+
+
+def latest_after_top_three(scores):
+    pass
+
+
+def scores_after_top_three(scores):
+    pass
diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md
index 9cc5350b..5e488447 100644
--- a/exercises/practice/isogram/.docs/instructions.md
+++ b/exercises/practice/isogram/.docs/instructions.md
@@ -2,7 +2,7 @@
 
 Determine if a word or phrase is an isogram.
 
-An isogram (also known as a "nonpattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times.
+An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times.
 
 Examples of isograms:
 
diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2
index 1b8eb014..fbec4a93 100644
--- a/exercises/practice/isogram/.meta/template.j2
+++ b/exercises/practice/isogram/.meta/template.j2
@@ -17,5 +17,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{ case["expected"] }}
         )
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2
index 9b7afbbe..f1dc5425 100644
--- a/exercises/practice/kindergarten-garden/.meta/template.j2
+++ b/exercises/practice/kindergarten-garden/.meta/template.j2
@@ -42,7 +42,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
     {%- endfor %}
     {%- endif %}
-
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md
index c7c7d3e0..f1215dd3 100644
--- a/exercises/practice/luhn/.docs/instructions.md
+++ b/exercises/practice/luhn/.docs/instructions.md
@@ -9,14 +9,13 @@ Numbers.
 
 The task is to check if a given string is valid.
 
-Validating a Number
-------
+## Validating a Number
 
 Strings of length 1 or less are not valid. Spaces are allowed in the input,
 but they should be stripped before checking. All other non-digit characters
 are disallowed.
 
-## Example 1: valid credit card number
+### Example 1: valid credit card number
 
 ```text
 4539 3195 0343 6467
@@ -44,7 +43,7 @@ Then sum all of the digits:
 
 If the sum is evenly divisible by 10, then the number is valid. This number is valid!
 
-## Example 2: invalid credit card number
+### Example 2: invalid credit card number
 
 ```text
 8273 1232 7352 0569
diff --git a/exercises/practice/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2
index 7e6c05fe..af8489b3 100644
--- a/exercises/practice/luhn/.meta/template.j2
+++ b/exercises/practice/luhn/.meta/template.j2
@@ -41,5 +41,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         number = Luhn("055 444 285")
         self.assertIs(number.valid(), True)
         self.assertIs(number.valid(), True)
-
-{{ macros.footer() }}
diff --git a/exercises/practice/markdown/.meta/template.j2 b/exercises/practice/markdown/.meta/template.j2
index da108136..a88bf822 100644
--- a/exercises/practice/markdown/.meta/template.j2
+++ b/exercises/practice/markdown/.meta/template.j2
@@ -3,6 +3,8 @@
 
 class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
+    {% if case["description"] == "with h7 header level" %}
+    {% else %}
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual(
             {{ case["property"] | to_snake }}(
@@ -10,6 +12,5 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             ),
             "{{ case["expected"] }}"
         )
+    {% endif %}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md
index b5cb1657..4f33a1f2 100644
--- a/exercises/practice/prime-factors/.docs/instructions.md
+++ b/exercises/practice/prime-factors/.docs/instructions.md
@@ -6,25 +6,22 @@ A prime number is only evenly divisible by itself and 1.
 
 Note that 1 is not a prime number.
 
-## Example
-
+Example
 What are the prime factors of 60?
 
-- Our first divisor is 2. 2 goes into 60, leaving 30.
-- 2 goes into 30, leaving 15.
-  - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3.
-- 3 goes cleanly into 15, leaving 5.
-  - 3 does not go cleanly into 5. The next possible factor is 4.
-  - 4 does not go cleanly into 5. The next possible factor is 5.
-- 5 does go cleanly into 5.
-- We're left only with 1, so now, we're done.
-
-Our successful divisors in that computation represent the list of prime
-factors of 60: 2, 2, 3, and 5.
+Our first divisor is 2. 2 goes into 60, leaving 30.
+2 goes into 30, leaving 15.
+2 doesn't go cleanly into 15. So let's move on to our next divisor, 3.
+3 goes cleanly into 15, leaving 5.
+3 does not go cleanly into 5. The next possible factor is 4.
+4 does not go cleanly into 5. The next possible factor is 5.
+5 does go cleanly into 5.
+We're left only with 1, so now, we're done.
+Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5.
 
 You can check this yourself:
 
-- 2 * 2 * 3 * 5
-- = 4 * 15
-- = 60
-- Success!
+2 * 2 * 3 * 5
+= 4 * 15
+= 60
+Success!
diff --git a/exercises/practice/prime-factors/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2
index dd9ad27d..4178af79 100644
--- a/exercises/practice/prime-factors/.meta/template.j2
+++ b/exercises/practice/prime-factors/.meta/template.j2
@@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         def test_{{ case["description"] | to_snake }}(self):
             self.assertEqual({{ case["property"] }}({{ case["input"]["value"] }}), {{ case["expected"] }})
         {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/prime-factors/prime_factors.py b/exercises/practice/prime-factors/prime_factors.py
index c3b672cf..c6331342 100644
--- a/exercises/practice/prime-factors/prime_factors.py
+++ b/exercises/practice/prime-factors/prime_factors.py
@@ -1,2 +1,13 @@
+# def factors(value):
+#     pass
 def factors(value):
-    pass
+    factors = []
+    divisor = 2
+    while value > 1:
+        while value % divisor == 0:
+            factors.append(divisor)
+            value /= divisor
+
+        divisor += 1
+
+    return factors
diff --git a/exercises/practice/queen-attack/.docs/instructions.append.md b/exercises/practice/queen-attack/.docs/instructions.append.md
index 894b2936..7d371f1e 100644
--- a/exercises/practice/queen-attack/.docs/instructions.append.md
+++ b/exercises/practice/queen-attack/.docs/instructions.append.md
@@ -4,7 +4,7 @@
 
 Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
 
-This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" `ValueErrors` if the `Queen` constructor is passed numbers that are negative, or numbers that are not a valid row or column.  The tests will only pass if you both `raise` the `exception` and include a message with it.
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" `ValueErrors` if the `Queen` constructor is passed numbers that are negative, or numbers that are not a valid row or column.  The tests will only pass if you both `raise` the `exception` and include a message with it. You also should `raise` a `ValueError` if both the queens passed to the `can_attack()` method are on the same location.
 
 To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
 
@@ -20,4 +20,7 @@ raise ValueError("column not positive")
 
 # if the column parameter is not on the defined board
 raise ValueError("column not on board")
+
+# if both the queens are on the same location
+raise ValueError("Invalid queen position: both queens in the same square")
 ```
diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md
index 1a7d3f3b..d0c2e3ec 100644
--- a/exercises/practice/rational-numbers/.docs/instructions.md
+++ b/exercises/practice/rational-numbers/.docs/instructions.md
@@ -2,6 +2,12 @@
 
 A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`.
 
+~~~~exercism/note
+Note that mathematically, the denominator can't be zero.
+However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers.
+In those cases, the denominator and numerator generally still can't both be zero at once.
+~~~~
+
 The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`.
 
 The sum of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ + r₂ = a₁/b₁ + a₂/b₂ = (a₁ * b₂ + a₂ * b₁) / (b₁ * b₂)`.
@@ -21,8 +27,9 @@ Exponentiation of a rational number `r = a/b` to a real (floating-point) number
 Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`.
 
 Implement the following operations:
- - addition, subtraction, multiplication and division of two rational numbers,
- - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
+
+- addition, subtraction, multiplication and division of two rational numbers,
+- absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
 
 Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`.
 
diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md
index 85cf1130..68550f78 100644
--- a/exercises/practice/resistor-color-duo/.docs/instructions.md
+++ b/exercises/practice/resistor-color-duo/.docs/instructions.md
@@ -17,18 +17,17 @@ The program will take color names as input and output a two digit number, even i
 
 The band colors are encoded as follows:
 
-- Black: 0
-- Brown: 1
-- Red: 2
-- Orange: 3
-- Yellow: 4
-- Green: 5
-- Blue: 6
-- Violet: 7
-- Grey: 8
-- White: 9
+* Black: 0
+* Brown: 1
+* Red: 2
+* Orange: 3
+* Yellow: 4
+* Green: 5
+* Blue: 6
+* Violet: 7
+* Grey: 8
+* White: 9
 
 From the example above:
 brown-green should return 15
 brown-green-violet should return 15 too, ignoring the third color.
-
diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2
index 180a51d7..cabf271c 100644
--- a/exercises/practice/resistor-color-duo/.meta/template.j2
+++ b/exercises/practice/resistor-color-duo/.meta/template.j2
@@ -13,6 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
         {{ test_case(case) }}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md
new file mode 100644
index 00000000..aed8f067
--- /dev/null
+++ b/exercises/practice/say/.docs/instructions.append.md
@@ -0,0 +1,17 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the number input to `say()` is out of range. The tests will only pass if you both `raise` the `exception` and include a message with it.
+
+To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
+
+```python
+# if the number is negative
+raise ValueError("input out of range")
+
+# if the number is larger than 999,999,999,99
+raise ValueError("input out of range")
+```
diff --git a/exercises/practice/say/.meta/example.py b/exercises/practice/say/.meta/example.py
index 5c44fccb..30ad378f 100644
--- a/exercises/practice/say/.meta/example.py
+++ b/exercises/practice/say/.meta/example.py
@@ -1,8 +1,8 @@
 def say(number, recursive=False):
     small = dict(enumerate((
-        'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
-        'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen',
-        'sixteen', 'seventeen', 'eighteen', 'nineteen')))
+            'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+            'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen',
+            'sixteen', 'seventeen', 'eighteen', 'nineteen')))
 
     tens = {20: 'twenty', 30: 'thirty', 40: 'forty', 50: 'fifty',
             60: 'sixty', 70: 'seventy', 80: 'eighty', 90: 'ninety'}
@@ -10,9 +10,9 @@ def say(number, recursive=False):
     k, m, b, t = 1e3, 1e6, 1e9, 1e12
 
     if number < 0:
-        raise ValueError('number is negative')
+        raise ValueError("input out of range")
     if number >= t:
-        raise ValueError('number is too large: {}'.format(number))
+        raise ValueError("input out of range")
 
     if number < 20:
         return small[number] if not recursive else  + small[number]
diff --git a/exercises/practice/say/.meta/template.j2 b/exercises/practice/say/.meta/template.j2
index a29d2ddc..77fa67a1 100644
--- a/exercises/practice/say/.meta/template.j2
+++ b/exercises/practice/say/.meta/template.j2
@@ -12,8 +12,10 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
     def test_{{ case["description"] | to_snake }}(self):
         {%- if case is error_case %}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{- test_call(case) }}
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
         {%- else %}
         self.assertEqual({{- test_call(case) }}, "{{ case["expected"] }}")
         {%- endif %}
@@ -23,11 +25,11 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in additional_cases -%}
     def test_{{ case["description"] | to_snake }}(self):
         {%- if case is error_case %}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{- test_call(case) }}
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
         {%- else %}
         self.assertEqual({{- test_call(case) }}, "{{ case["expected"] }}")
         {%- endif %}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/series/.docs/instructions.append.md b/exercises/practice/series/.docs/instructions.append.md
new file mode 100644
index 00000000..1e76e546
--- /dev/null
+++ b/exercises/practice/series/.docs/instructions.append.md
@@ -0,0 +1,23 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the series and/or length parameters input into the `slice()` function are empty or out of range. The tests will only pass if you both `raise` the `exception` and include a message with it.
+
+To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
+
+```python
+# if the slice length is zero.
+raise ValueError("slice length cannot be zero")
+
+# if the slice length is negative.
+raise ValueError("slice length cannot be negative")
+
+# if the series provided is empty.
+raise ValueError("series cannot be empty")
+
+# if the slice length is longer than the series.
+raise ValueError("slice length cannot be greater than series length")
+```
diff --git a/exercises/practice/series/.meta/example.py b/exercises/practice/series/.meta/example.py
index 305ce675..a5ecfe35 100644
--- a/exercises/practice/series/.meta/example.py
+++ b/exercises/practice/series/.meta/example.py
@@ -1,5 +1,11 @@
 def slices(series, length):
-    if not 1 <= length <= len(series):
-        raise ValueError("Invalid slice length for this series: " + str(
-            length))
+    if not series:
+        raise ValueError("series cannot be empty")
+    elif length == 0:
+        raise ValueError("slice length cannot be zero")
+    elif length < 0:
+        raise ValueError("slice length cannot be negative")
+    elif len(series) < length:
+        raise ValueError("slice length cannot be greater than series length")
+
     return [series[i:i + length] for i in range(len(series) - length + 1)]
diff --git a/exercises/practice/series/.meta/template.j2 b/exercises/practice/series/.meta/template.j2
index 62de39e9..2e7f60e4 100644
--- a/exercises/practice/series/.meta/template.j2
+++ b/exercises/practice/series/.meta/template.j2
@@ -7,10 +7,11 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% set expected = case["expected"] -%}
     def test_{{ case["description"] | to_snake }}(self):
         {% if expected["error"] -%}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{ case["property"] }}("{{ input["series"] }}", {{ input["sliceLength"] }})
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
         {% else -%}
         self.assertEqual({{ case["property"] }}("{{ input["series"] }}", {{ input["sliceLength"] }}), {{ case["expected"] }})
         {% endif %}
     {% endfor %}
-{{ macros.footer() }}
diff --git a/exercises/practice/sgf-parsing/.docs/instructions.append.md b/exercises/practice/sgf-parsing/.docs/instructions.append.md
new file mode 100644
index 00000000..080457bc
--- /dev/null
+++ b/exercises/practice/sgf-parsing/.docs/instructions.append.md
@@ -0,0 +1,23 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the input lacks proper delimiters, is not in uppercase, does not form a tree with nodes, or does not form a tree at all. The tests will only pass if you both `raise` the `exception` and include a message with it.
+
+To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
+
+```python
+# if the tree properties as given do not have proper delimiters.
+raise ValueError("properties without delimiter")
+
+# if the tree properties as given are not all in uppercase.
+raise ValueError("property must be in uppercase")
+
+# if the input does not form a tree, or is empty.
+raise ValueError("tree missing")
+
+# if the input is a tree without any nodes.
+raise ValueError("tree with no nodes")
+```
diff --git a/exercises/practice/sgf-parsing/.meta/example.py b/exercises/practice/sgf-parsing/.meta/example.py
index dfcf8ac8..b1642c3a 100644
--- a/exercises/practice/sgf-parsing/.meta/example.py
+++ b/exercises/practice/sgf-parsing/.meta/example.py
@@ -6,98 +6,100 @@ class SgfTree:
     def __eq__(self, other):
         if not isinstance(other, SgfTree):
             return False
-        for k, v in self.properties.items():
-            if k not in other.properties:
+
+        for key, value in self.properties.items():
+            if key not in other.properties:
                 return False
-            if other.properties[k] != v:
+
+            if other.properties[key] != value:
                 return False
-        for k in other.properties.keys():
-            if k not in self.properties:
+
+        for key in other.properties.keys():
+            if key not in self.properties:
                 return False
+
         if len(self.children) != len(other.children):
             return False
-        for a, b in zip(self.children, other.children):
-            if not (a == b):
+
+        for child, other_child in zip(self.children, other.children):
+            if not (child == other_child):
                 return False
         return True
 
     def __repr__(self):
-        """Ironically, encoding to SGF is much easier"""
+        """Ironically, encoding to SGF is much easier."""
         rep = '(;'
-        for k, vs in self.properties.items():
-            rep += k
-            for v in vs:
-                rep += '[{}]'.format(v)
+        for key, property_value in self.properties.items():
+            rep += key
+            for value in property_value:
+                rep += '[{}]'.format(value)
         if self.children:
             if len(self.children) > 1:
                 rep += '('
-            for c in self.children:
-                rep += repr(c)[1:-1]
+            for child in self.children:
+                rep += repr(child)[1:-1]
             if len(self.children) > 1:
                 rep += ')'
         return rep + ')'
 
 
-def is_upper(s):
-    a, z = map(ord, 'AZ')
-    return all(
-        a <= o and o <= z
-        for o in map(ord, s)
-    )
-
-
 def parse(input_string):
     root = None
     current = None
     stack = list(input_string)
 
-    def assert_that(condition):
-        if not condition:
-            raise ValueError(
-                'invalid format at {}:{}: {}'.format(
-                    repr(input_string),
-                    len(input_string) - len(stack),
-                    repr(''.join(stack))
-                )
-            )
-    assert_that(stack)
+    if input_string == "()":
+        raise ValueError("tree with no nodes")
+
+    if not stack:
+        raise ValueError("tree missing")
 
     def pop():
         if stack[0] == '\\':
             stack.pop(0)
-        ch = stack.pop(0)
-        return ' ' if ch in ['\t'] else ch
+        characters = stack.pop(0)
+        return ' ' if characters in ['\t'] else characters
 
-    def peek():
-        return stack[0]
-
-    def pop_until(ch):
+    def pop_until(delimiter):
         try:
-            v = ''
-            while peek() != ch:
-                v += pop()
-            return v
+            value = ''
+            while stack[0] != delimiter:
+                value += pop()
+            return value
         except IndexError:
-            raise ValueError('Unable to find {}'.format(ch))
+            raise ValueError("properties without delimiter")
+
     while stack:
-        assert_that(pop() == '(' and peek() == ';')
-        while pop() == ';':
-            properties = {}
-            while is_upper(peek()):
-                key = pop_until('[')
-                assert_that(is_upper(key))
-                values = []
-                while peek() == '[':
-                    pop()
-                    values.append(pop_until(']'))
-                    pop()
-                properties[key] = values
-            if root is None:
-                current = root = SgfTree(properties)
-            else:
-                current = SgfTree(properties)
-                root.children.append(current)
-            while peek() == '(':
-                child_input = pop() + pop_until(')') + pop()
-                current.children.append(parse(child_input))
+        if pop() == '(' and stack[0] == ';':
+            while pop() == ';':
+                properties = {}
+                while stack[0].isupper():
+                    key = pop_until('[')
+                    if not key.isupper():
+                        raise ValueError("property must be in uppercase")
+                    values = []
+                    while stack[0] == '[':
+                        pop()
+                        values.append(pop_until(']'))
+                        pop()
+                    properties[key] = values
+
+                if stack[0].isalpha():
+                    if not stack[0].isupper():
+                        raise ValueError("property must be in uppercase")
+
+                if root is None:
+                    current = root = SgfTree(properties)
+
+                else:
+                    current = SgfTree(properties)
+                    root.children.append(current)
+
+                while stack[0] == '(':
+                    child_input = pop() + pop_until(')') + pop()
+                    current.children.append(parse(child_input))
+
+        elif root is None and current is None:
+            raise ValueError("tree missing")
+
     return root
diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2
index 819f53eb..f3da29b3 100644
--- a/exercises/practice/sgf-parsing/.meta/template.j2
+++ b/exercises/practice/sgf-parsing/.meta/template.j2
@@ -4,8 +4,10 @@
     def test_{{ case["description"] | to_snake }}(self):
         input_string = '{{ case["input"]["encoded"] | escape_invalid_escapes }}'
         {% if case["expected"]["error"] -%}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{ case["property"] | to_snake }}(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
         {% else -%}
         expected = {{ sgftree(case["expected"]) }}
         self.assertEqual({{ case["property"] | to_snake }}(input_string), expected)
@@ -43,5 +45,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases %}
     {{ test_case(case) }}
     {%- endfor -%}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/sgf-parsing/sgf_parsing.py b/exercises/practice/sgf-parsing/sgf_parsing.py
index ce54bd27..fa30f3f5 100644
--- a/exercises/practice/sgf-parsing/sgf_parsing.py
+++ b/exercises/practice/sgf-parsing/sgf_parsing.py
@@ -6,18 +6,18 @@ class SgfTree:
     def __eq__(self, other):
         if not isinstance(other, SgfTree):
             return False
-        for k, v in self.properties.items():
-            if k not in other.properties:
+        for key, value in self.properties.items():
+            if key not in other.properties:
                 return False
-            if other.properties[k] != v:
+            if other.properties[key] != value:
                 return False
-        for k in other.properties.keys():
-            if k not in self.properties:
+        for key in other.properties.keys():
+            if key not in self.properties:
                 return False
         if len(self.children) != len(other.children):
             return False
-        for a, b in zip(self.children, other.children):
-            if a != b:
+        for child, other_child in zip(self.children, other.children):
+            if child != other_child:
                 return False
         return True
 
diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md
index 28e754d9..4ca3f215 100644
--- a/exercises/practice/simple-linked-list/.docs/instructions.append.md
+++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md
@@ -1,8 +1,37 @@
-# Hints
+# Instructions append
+
+## Customizing and Raising Exceptions
+
+Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging.
+
+Custom exceptions can be created through new exception classes (see [`classes`](https://docs.python.org/3/tutorial/classes.html#tut-classes) for more detail.) that are typically subclasses of [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception).
+
+For situations where you know the error source will be a derivative of a certain exception _type_, you can choose to inherit from one of the [`built in error types`](https://docs.python.org/3/library/exceptions.html#base-classes) under the _Exception_ class. When raising the error, you should still include a meaningful message.
+
+This particular exercise requires that you create a _custom exception_ to be [raised](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)/"thrown" when your linked list is **empty**. The tests will only pass if you customize appropriate exceptions, `raise` those exceptions, and include appropriate error messages.
+
+To customize a generic _exception_, create a `class` that inherits from `Exception`. When raising the custom exception with a message, write the message as an argument to the `exception` type:
+
+```python
+# subclassing Exception to create EmptyListException
+class EmptyListException(Exception):
+    """Exception raised when the linked list is empty.
+
+    message: explanation of the error.
+
+    """
+    def __init__(self, message):
+        self.message = message
+        
+# raising an EmptyListException
+raise EmptyListException("The list is empty.")
+```
+
+## Additional Hints
 
 To support `list()`, see [implementing an iterator for a class.](https://docs.python.org/3/tutorial/classes.html#iterators)
 
-Additionally, note that Python2's `next()` has been replaced by `__next__()` in Python3. For dual compatibility, `next()` can be implemented as:
+Note that Python2's `next()` was replaced by `__next__()` in Python3. If dual compatibility is needed, `next()` can be implemented as:
 
 ```Python
 def next(self):
diff --git a/exercises/practice/simple-linked-list/.meta/example.py b/exercises/practice/simple-linked-list/.meta/example.py
index 37237646..2b25ac0a 100644
--- a/exercises/practice/simple-linked-list/.meta/example.py
+++ b/exercises/practice/simple-linked-list/.meta/example.py
@@ -42,7 +42,7 @@ class LinkedList:
 
     def head(self):
         if self._head is None:
-            raise EmptyListException("The list is empty")
+            raise EmptyListException("The list is empty.")
         return self._head
 
     def push(self, value):
@@ -53,7 +53,7 @@ class LinkedList:
 
     def pop(self):
         if self._head is None:
-            raise EmptyListException("The list is empty")
+            raise EmptyListException("The list is empty.")
         self._len -= 1
         ret = self._head.value()
         self._head = self._head.next()
@@ -64,4 +64,11 @@ class LinkedList:
 
 
 class EmptyListException(Exception):
-    pass
+    """Exception raised when the linked list is empty.
+
+    message: explanation of the error.
+
+    """
+
+    def __init__(self, message):
+        self.message = message
diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md
index 45c3b964..7535931a 100644
--- a/exercises/practice/sublist/.docs/instructions.md
+++ b/exercises/practice/sublist/.docs/instructions.md
@@ -1,18 +1,25 @@
 # Instructions
 
-Given two lists determine if the first list is contained within the second
-list, if the second list is contained within the first list, if both lists are
-contained within each other or if none of these are true.
+Given any two lists `A` and `B`, determine if:
 
-Specifically, a list A is a sublist of list B if by dropping 0 or more elements
-from the front of B and 0 or more elements from the back of B you get a list
-that's completely equal to A.
+- List `A` is equal to list `B`; or
+- List `A` contains list `B` (`A` is a superlist of `B`); or
+- List `A` is contained by list `B` (`A` is a sublist of `B`); or
+- None of the above is true, thus lists `A` and `B` are unequal
+
+Specifically, list `A` is equal to list `B` if both lists have the same values in the same order.
+List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`.
+List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`.
 
 Examples:
 
- * A = [1, 2, 3], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [3, 4, 5], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [3, 4], B = [1, 2, 3, 4, 5], A is a sublist of B
- * A = [1, 2, 3], B = [1, 2, 3], A is equal to B
- * A = [1, 2, 3, 4, 5], B = [2, 3, 4], A is a superlist of B
- * A = [1, 2, 4], B = [1, 2, 3, 4, 5], A is not a superlist of, sublist of or equal to B
+- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal
+- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B`
+- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B`
+- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B`
+- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal
+- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B`
+- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal
+- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal
diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2
index 051b16ec..00c111f3 100644
--- a/exercises/practice/sublist/.meta/template.j2
+++ b/exercises/practice/sublist/.meta/template.j2
@@ -20,4 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2
index 9271b9e7..30ca2e94 100644
--- a/exercises/practice/tournament/.meta/template.j2
+++ b/exercises/practice/tournament/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}(results), table)
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2
index 0b6f54f7..ef724132 100644
--- a/exercises/practice/transpose/.meta/template.j2
+++ b/exercises/practice/transpose/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}("\n".join(lines)), "\n".join(expected))
 
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/tree-building/.docs/instructions.append.md b/exercises/practice/tree-building/.docs/instructions.append.md
new file mode 100644
index 00000000..4bcc8674
--- /dev/null
+++ b/exercises/practice/tree-building/.docs/instructions.append.md
@@ -0,0 +1,7 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you refactor how/where the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) is used to "throw" a `ValueError` for invalid tree input. The tests will only pass if the code both `raise`s the appropriate `exception` and includes an appropriate message with it.
diff --git a/exercises/practice/tree-building/.meta/example.py b/exercises/practice/tree-building/.meta/example.py
index a6059d29..00196a33 100644
--- a/exercises/practice/tree-building/.meta/example.py
+++ b/exercises/practice/tree-building/.meta/example.py
@@ -15,15 +15,17 @@ class Node:
 
 def validateRecord(record):
     if record.equal_id() and record.record_id != 0:
-        raise ValueError("Only root should have equal record and parent id")
-    elif not record.equal_id() and record.parent_id >= record.record_id:
-        raise ValueError("Node record_id should be smaller than its parent_id")
+        raise ValueError("Only root should have equal record and parent id.")
+
+    if not record.equal_id() and record.parent_id >= record.record_id:
+        raise ValueError("Node record_id should be smaller than it's parent_id.")
 
 
 def BuildTree(records):
     parent_dict = {}
     node_dict = {}
     ordered_id = sorted((i.record_id for i in records))
+
     for record in records:
         validateRecord(record)
         parent_dict[record.record_id] = record.parent_id
@@ -34,7 +36,8 @@ def BuildTree(records):
 
     for index, record_id in enumerate(ordered_id):
         if index != record_id:
-            raise ValueError("Record id is invalid or out of order")
+            raise ValueError("Record id is invalid or out of order.")
+
         if record_id == root_id:
             root = node_dict[record_id]
         else:
diff --git a/exercises/practice/tree-building/tree_building.py b/exercises/practice/tree-building/tree_building.py
index d5414c07..75082a66 100644
--- a/exercises/practice/tree-building/tree_building.py
+++ b/exercises/practice/tree-building/tree_building.py
@@ -16,9 +16,9 @@ def BuildTree(records):
     ordered_id = [i.record_id for i in records]
     if records:
         if ordered_id[-1] != len(ordered_id) - 1:
-            raise ValueError('Tree must be continuous')
+            raise ValueError('broken tree')
         if ordered_id[0] != 0:
-            raise ValueError('Tree must start with id 0')
+            raise ValueError('invalid')
     trees = []
     parent = {}
     for i in range(len(ordered_id)):
@@ -26,12 +26,12 @@ def BuildTree(records):
             if ordered_id[i] == j.record_id:
                 if j.record_id == 0:
                     if j.parent_id != 0:
-                        raise ValueError('Root node cannot have a parent')
+                        raise ValueError('error!')
                 if j.record_id < j.parent_id:
-                    raise ValueError('Parent id must be lower than child id')
+                    raise ValueError('something went wrong!')
                 if j.record_id == j.parent_id:
                     if j.record_id != 0:
-                        raise ValueError('Tree is a cycle')
+                        raise ValueError('error!')
                 trees.append(Node(ordered_id[i]))
     for i in range(len(ordered_id)):
         for j in trees:
diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.append.md b/exercises/practice/variable-length-quantity/.docs/instructions.append.md
new file mode 100644
index 00000000..a1440dce
--- /dev/null
+++ b/exercises/practice/variable-length-quantity/.docs/instructions.append.md
@@ -0,0 +1,14 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the sequence to decode or encode is incomplete. The tests will only pass if you both `raise` the `exception` and include a message with it.
+
+To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
+
+```python
+# if the sequence is incomplete
+raise ValueError("incomplete sequence")
+```
diff --git a/exercises/practice/variable-length-quantity/.meta/example.py b/exercises/practice/variable-length-quantity/.meta/example.py
index 5a59cb7f..d85206aa 100644
--- a/exercises/practice/variable-length-quantity/.meta/example.py
+++ b/exercises/practice/variable-length-quantity/.meta/example.py
@@ -29,6 +29,6 @@ def decode(bytes_):
             values.append(n)
             n = 0
         elif i == len(bytes_) - 1:
-            raise ValueError('incomplete byte sequence')
+            raise ValueError("incomplete sequence")
 
     return values
diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2
index c557aa15..10b43cbd 100644
--- a/exercises/practice/variable-length-quantity/.meta/template.j2
+++ b/exercises/practice/variable-length-quantity/.meta/template.j2
@@ -15,13 +15,13 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         {%- for sub_case in case.cases %}
     def test_{{ sub_case["description"] | to_snake }}(self):
         {%- if sub_case is error_case %}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{ sub_case["property"] }}({{ list_int_to_hex(sub_case["input"]["integers"]) }})
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ sub_case["expected"]["error"] }}")
         {%- else %}
         self.assertEqual({{ sub_case["property"] }}({{ list_int_to_hex(sub_case["input"]["integers"]) }}), {{ list_int_to_hex(sub_case["expected"]) }})
         {%- endif %}
         {% endfor -%}
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/wordy/.docs/instructions.append.md b/exercises/practice/wordy/.docs/instructions.append.md
new file mode 100644
index 00000000..0a0d309f
--- /dev/null
+++ b/exercises/practice/wordy/.docs/instructions.append.md
@@ -0,0 +1,17 @@
+# Instructions append
+
+## Exception messages
+
+Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
+
+This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it.
+
+To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
+
+```python
+# if the question contains an unknown operation.
+raise ValueError("unknown operation")
+
+# if the question is malformed or invalid.
+raise ValueError("syntax error")
+```
diff --git a/exercises/practice/wordy/.meta/example.py b/exercises/practice/wordy/.meta/example.py
index 922c5175..9542334d 100644
--- a/exercises/practice/wordy/.meta/example.py
+++ b/exercises/practice/wordy/.meta/example.py
@@ -1,10 +1,5 @@
-import sys
 from operator import add, mul, sub
-
-if sys.version_info[0] == 2:
-    from operator import div
-else:
-    from operator import floordiv as div
+from operator import floordiv as div
 
 
 VALID_OPERATIONS = {"plus": add, "minus": sub,
@@ -12,16 +7,21 @@ VALID_OPERATIONS = {"plus": add, "minus": sub,
 
 
 def answer(question):
-    if not (question.startswith("What is ") and question.endswith("?")):
-        raise ValueError("Ill-formed question")
-    words = question[8:-1].strip().lower().split()
-    if not words:
-        raise ValueError("Ill-formed question")
-    words.reverse()
+    if not bool(question[8:-1].strip().lower().split()):
+        raise ValueError("syntax error")
+
+    elif not question.startswith("What is "):
+        raise ValueError("unknown operation")
+
+    else:
+        words = question[8:-1].strip().lower().split()
+        words.reverse()
+
     try:
         main_value = int(words.pop())
     except ValueError:
-        raise ValueError("Ill-formed question")
+        raise ValueError("syntax error")
+
     while words:
         operation = [words.pop()]
         while words:
@@ -30,12 +30,21 @@ def answer(question):
                 second_value = int(next_to_evaluate)
                 break
             except ValueError:
-                operation.append(next_to_evaluate)
+                if next_to_evaluate == operation[-1]:
+                    raise ValueError("syntax error")
+                else:
+                    operation.append(next_to_evaluate)
         else:
-            raise ValueError("Ill-formed question")
+            if operation[-1] not in VALID_OPERATIONS and not operation[-1].isdigit() :
+                raise ValueError("unknown operation")
+            else:
+                raise ValueError("syntax error")
+
         operation = " ".join(operation)
+
         try:
             main_value = VALID_OPERATIONS[operation](main_value, second_value)
         except KeyError:
-            raise ValueError("Ill-formed question")
+            raise ValueError("unknown operation")
+
     return main_value
diff --git a/exercises/practice/wordy/.meta/template.j2 b/exercises/practice/wordy/.meta/template.j2
index 3111e6bb..00b70a8b 100644
--- a/exercises/practice/wordy/.meta/template.j2
+++ b/exercises/practice/wordy/.meta/template.j2
@@ -3,8 +3,10 @@
     def test_{{ case["description"] | to_snake }}(self):
         {%- set question = case["input"]["question"] %}
         {%- if case is error_case %}
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             {{ case["property"] }}("{{ question }}")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
         {%- else %}
         self.assertEqual({{ case["property"] }}("{{ question }}"), {{ case["expected"] }})
         {%- endif %}
@@ -23,6 +25,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-
-
-{{ macros.footer() }}


Test patch:
diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml
index 7fb93f71..6c507d25 100644
--- a/exercises/practice/anagram/.meta/tests.toml
+++ b/exercises/practice/anagram/.meta/tests.toml
@@ -1,12 +1,24 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [dd40c4d2-3c8b-44e5-992a-f42b393ec373]
 description = "no matches"
 
 [b3cca662-f50a-489e-ae10-ab8290a09bdc]
 description = "detects two anagrams"
+include = false
+
+[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b]
+description = "detects two anagrams"
+reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc"
 
 [a27558ee-9ba0-4552-96b1-ecf665b06556]
 description = "does not detect anagram subsets"
diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py
index 241a6f4b..733deac1 100644
--- a/exercises/practice/anagram/anagram_test.py
+++ b/exercises/practice/anagram/anagram_test.py
@@ -18,6 +18,11 @@ class AnagramTest(unittest.TestCase):
         expected = ["stream", "maters"]
         self.assertCountEqual(find_anagrams("master", candidates), expected)
 
+    def test_detects_two_anagrams(self):
+        candidates = ["lemons", "cherry", "melons"]
+        expected = ["lemons", "melons"]
+        self.assertCountEqual(find_anagrams("solemn", candidates), expected)
+
     def test_does_not_detect_anagram_subsets(self):
         candidates = ["dog", "goody"]
         expected = []
diff --git a/exercises/practice/book-store/.meta/tests.toml b/exercises/practice/book-store/.meta/tests.toml
index 9c7bc8d0..a7b7e0c2 100644
--- a/exercises/practice/book-store/.meta/tests.toml
+++ b/exercises/practice/book-store/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [17146bd5-2e80-4557-ab4c-05632b6b0d01]
 description = "Only a single book"
@@ -46,3 +53,12 @@ description = "Three each of first 2 books and 2 each of remaining books"
 
 [bb376344-4fb2-49ab-ab85-e38d8354a58d]
 description = "Four groups of four are cheaper than two groups each of five and three"
+
+[5260ddde-2703-4915-b45a-e54dbbac4303]
+description = "Check that groups of four are created properly even when there are more groups of three than groups of five"
+
+[b0478278-c551-4747-b0fc-7e0be3158b1f]
+description = "One group of one and four is cheaper than one group of two and three"
+
+[cf868453-6484-4ae1-9dfc-f8ee85bbde01]
+description = "One group of one and two plus three groups of four is cheaper than one group of each size"
diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py
index 7680e246..736b6ff9 100644
--- a/exercises/practice/book-store/book_store_test.py
+++ b/exercises/practice/book-store/book_store_test.py
@@ -70,6 +70,22 @@ class BookStoreTest(unittest.TestCase):
         basket = [1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]
         self.assertEqual(total(basket), 10240)
 
+    def test_check_that_groups_of_four_are_created_properly_even_when_there_are_more_groups_of_three_than_groups_of_five(
+        self,
+    ):
+        basket = [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5]
+        self.assertEqual(total(basket), 14560)
+
+    def test_one_group_of_one_and_four_is_cheaper_than_one_group_of_two_and_three(self):
+        basket = [1, 1, 2, 3, 4]
+        self.assertEqual(total(basket), 3360)
+
+    def test_one_group_of_one_and_two_plus_three_groups_of_four_is_cheaper_than_one_group_of_each_size(
+        self,
+    ):
+        basket = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5]
+        self.assertEqual(total(basket), 10000)
+
     # Additional tests for this track
 
     def test_two_groups_of_four_and_a_group_of_five(self):
diff --git a/exercises/practice/change/.meta/tests.toml b/exercises/practice/change/.meta/tests.toml
index 6d36d3c7..d2cf3ed9 100644
--- a/exercises/practice/change/.meta/tests.toml
+++ b/exercises/practice/change/.meta/tests.toml
@@ -1,6 +1,16 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[d0ebd0e1-9d27-4609-a654-df5c0ba1d83a]
+description = "change for 1 cent"
 
 [36887bea-7f92-4a9c-b0cc-c0e886b3ecc8]
 description = "single coin change"
diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py
index e31d5f48..6b335174 100644
--- a/exercises/practice/change/change_test.py
+++ b/exercises/practice/change/change_test.py
@@ -8,6 +8,9 @@ from change import (
 
 
 class ChangeTest(unittest.TestCase):
+    def test_change_for_1_cent(self):
+        self.assertEqual(find_fewest_coins([1, 5, 10, 25], 1), [1])
+
     def test_single_coin_change(self):
         self.assertEqual(find_fewest_coins([1, 5, 10, 25, 100], 25), [25])
 
diff --git a/exercises/practice/complex-numbers/.meta/tests.toml b/exercises/practice/complex-numbers/.meta/tests.toml
index d67ac05f..1b36ba3b 100644
--- a/exercises/practice/complex-numbers/.meta/tests.toml
+++ b/exercises/practice/complex-numbers/.meta/tests.toml
@@ -1,96 +1,127 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [9f98e133-eb7f-45b0-9676-cce001cd6f7a]
-description = "Real part of a purely real number"
+description = "Real part -> Real part of a purely real number"
 
 [07988e20-f287-4bb7-90cf-b32c4bffe0f3]
-description = "Real part of a purely imaginary number"
+description = "Real part -> Real part of a purely imaginary number"
 
 [4a370e86-939e-43de-a895-a00ca32da60a]
-description = "Real part of a number with real and imaginary part"
+description = "Real part -> Real part of a number with real and imaginary part"
 
 [9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6]
-description = "Imaginary part of a purely real number"
+description = "Imaginary part -> Imaginary part of a purely real number"
 
 [a8dafedd-535a-4ed3-8a39-fda103a2b01e]
-description = "Imaginary part of a purely imaginary number"
+description = "Imaginary part -> Imaginary part of a purely imaginary number"
 
 [0f998f19-69ee-4c64-80ef-01b086feab80]
-description = "Imaginary part of a number with real and imaginary part"
+description = "Imaginary part -> Imaginary part of a number with real and imaginary part"
 
 [a39b7fd6-6527-492f-8c34-609d2c913879]
 description = "Imaginary unit"
 
 [9a2c8de9-f068-4f6f-b41c-82232cc6c33e]
-description = "Add purely real numbers"
+description = "Arithmetic -> Addition -> Add purely real numbers"
 
 [657c55e1-b14b-4ba7-bd5c-19db22b7d659]
-description = "Add purely imaginary numbers"
+description = "Arithmetic -> Addition -> Add purely imaginary numbers"
 
 [4e1395f5-572b-4ce8-bfa9-9a63056888da]
-description = "Add numbers with real and imaginary part"
+description = "Arithmetic -> Addition -> Add numbers with real and imaginary part"
 
 [1155dc45-e4f7-44b8-af34-a91aa431475d]
-description = "Subtract purely real numbers"
+description = "Arithmetic -> Subtraction -> Subtract purely real numbers"
 
 [f95e9da8-acd5-4da4-ac7c-c861b02f774b]
-description = "Subtract purely imaginary numbers"
+description = "Arithmetic -> Subtraction -> Subtract purely imaginary numbers"
 
 [f876feb1-f9d1-4d34-b067-b599a8746400]
-description = "Subtract numbers with real and imaginary part"
+description = "Arithmetic -> Subtraction -> Subtract numbers with real and imaginary part"
 
 [8a0366c0-9e16-431f-9fd7-40ac46ff4ec4]
-description = "Multiply purely real numbers"
+description = "Arithmetic -> Multiplication -> Multiply purely real numbers"
 
 [e560ed2b-0b80-4b4f-90f2-63cefc911aaf]
-description = "Multiply purely imaginary numbers"
+description = "Arithmetic -> Multiplication -> Multiply purely imaginary numbers"
 
 [4d1d10f0-f8d4-48a0-b1d0-f284ada567e6]
-description = "Multiply numbers with real and imaginary part"
+description = "Arithmetic -> Multiplication -> Multiply numbers with real and imaginary part"
 
 [b0571ddb-9045-412b-9c15-cd1d816d36c1]
-description = "Divide purely real numbers"
+description = "Arithmetic -> Division -> Divide purely real numbers"
 
 [5bb4c7e4-9934-4237-93cc-5780764fdbdd]
-description = "Divide purely imaginary numbers"
+description = "Arithmetic -> Division -> Divide purely imaginary numbers"
 
 [c4e7fef5-64ac-4537-91c2-c6529707701f]
-description = "Divide numbers with real and imaginary part"
+description = "Arithmetic -> Division -> Divide numbers with real and imaginary part"
 
 [c56a7332-aad2-4437-83a0-b3580ecee843]
-description = "Absolute value of a positive purely real number"
+description = "Absolute value -> Absolute value of a positive purely real number"
 
 [cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c]
-description = "Absolute value of a negative purely real number"
+description = "Absolute value -> Absolute value of a negative purely real number"
 
 [bbe26568-86c1-4bb4-ba7a-da5697e2b994]
-description = "Absolute value of a purely imaginary number with positive imaginary part"
+description = "Absolute value -> Absolute value of a purely imaginary number with positive imaginary part"
 
 [3b48233d-468e-4276-9f59-70f4ca1f26f3]
-description = "Absolute value of a purely imaginary number with negative imaginary part"
+description = "Absolute value -> Absolute value of a purely imaginary number with negative imaginary part"
 
 [fe400a9f-aa22-4b49-af92-51e0f5a2a6d3]
-description = "Absolute value of a number with real and imaginary part"
+description = "Absolute value -> Absolute value of a number with real and imaginary part"
 
 [fb2d0792-e55a-4484-9443-df1eddfc84a2]
-description = "Conjugate a purely real number"
+description = "Complex conjugate -> Conjugate a purely real number"
 
 [e37fe7ac-a968-4694-a460-66cb605f8691]
-description = "Conjugate a purely imaginary number"
+description = "Complex conjugate -> Conjugate a purely imaginary number"
 
 [f7704498-d0be-4192-aaf5-a1f3a7f43e68]
-description = "Conjugate a number with real and imaginary part"
+description = "Complex conjugate -> Conjugate a number with real and imaginary part"
 
 [6d96d4c6-2edb-445b-94a2-7de6d4caaf60]
-description = "Euler's identity/formula"
+description = "Complex exponential function -> Euler's identity/formula"
 
 [2d2c05a0-4038-4427-a24d-72f6624aa45f]
-description = "Exponential of 0"
+description = "Complex exponential function -> Exponential of 0"
 
 [ed87f1bd-b187-45d6-8ece-7e331232c809]
-description = "Exponential of a purely real number"
+description = "Complex exponential function -> Exponential of a purely real number"
 
 [08eedacc-5a95-44fc-8789-1547b27a8702]
-description = "Exponential of a number with real and imaginary part"
+description = "Complex exponential function -> Exponential of a number with real and imaginary part"
+
+[06d793bf-73bd-4b02-b015-3030b2c952ec]
+description = "Operations between real numbers and complex numbers -> Add real number to complex number"
+
+[d77dbbdf-b8df-43f6-a58d-3acb96765328]
+description = "Operations between real numbers and complex numbers -> Add complex number to real number"
+
+[20432c8e-8960-4c40-ba83-c9d910ff0a0f]
+description = "Operations between real numbers and complex numbers -> Subtract real number from complex number"
+
+[b4b38c85-e1bf-437d-b04d-49bba6e55000]
+description = "Operations between real numbers and complex numbers -> Subtract complex number from real number"
+
+[dabe1c8c-b8f4-44dd-879d-37d77c4d06bd]
+description = "Operations between real numbers and complex numbers -> Multiply complex number by real number"
+
+[6c81b8c8-9851-46f0-9de5-d96d314c3a28]
+description = "Operations between real numbers and complex numbers -> Multiply real number by complex number"
+
+[8a400f75-710e-4d0c-bcb4-5e5a00c78aa0]
+description = "Operations between real numbers and complex numbers -> Divide complex number by real number"
+
+[9a867d1b-d736-4c41-a41e-90bd148e9d5e]
+description = "Operations between real numbers and complex numbers -> Divide real number by complex number"
diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py
index e0acc766..7580468d 100644
--- a/exercises/practice/complex-numbers/complex_numbers_test.py
+++ b/exercises/practice/complex-numbers/complex_numbers_test.py
@@ -1,4 +1,3 @@
-from __future__ import division
 import math
 
 import unittest
@@ -149,6 +148,32 @@ class ComplexNumbersTest(unittest.TestCase):
             ComplexNumber(math.log(2), math.pi).exp(), ComplexNumber(-2, 0)
         )
 
+    # Operations between real numbers and complex numbers
+
+    def test_add_real_number_to_complex_number(self):
+        self.assertEqual(ComplexNumber(1, 2) + 5, ComplexNumber(6, 2))
+
+    def test_add_complex_number_to_real_number(self):
+        self.assertEqual(5 + ComplexNumber(1, 2), ComplexNumber(6, 2))
+
+    def test_subtract_real_number_from_complex_number(self):
+        self.assertEqual(ComplexNumber(5, 7) - 4, ComplexNumber(1, 7))
+
+    def test_subtract_complex_number_from_real_number(self):
+        self.assertEqual(4 - ComplexNumber(5, 7), ComplexNumber(-1, -7))
+
+    def test_multiply_complex_number_by_real_number(self):
+        self.assertEqual(ComplexNumber(2, 5) * 5, ComplexNumber(10, 25))
+
+    def test_multiply_real_number_by_complex_number(self):
+        self.assertEqual(5 * ComplexNumber(2, 5), ComplexNumber(10, 25))
+
+    def test_divide_complex_number_by_real_number(self):
+        self.assertAlmostEqual(ComplexNumber(10, 100) / 10, ComplexNumber(1, 10))
+
+    def test_divide_real_number_by_complex_number(self):
+        self.assertAlmostEqual(5 / ComplexNumber(1, 1), ComplexNumber(2.5, -2.5))
+
     # Additional tests for this track
 
     def test_equality_of_complex_numbers(self):
diff --git a/exercises/practice/diffie-hellman/.meta/tests.toml b/exercises/practice/diffie-hellman/.meta/tests.toml
index e17d006e..a56c97fa 100644
--- a/exercises/practice/diffie-hellman/.meta/tests.toml
+++ b/exercises/practice/diffie-hellman/.meta/tests.toml
@@ -1,9 +1,16 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [1b97bf38-4307-418e-bfd2-446ffc77588d]
-description = "private key is in range 1 .. p"
+description = "private key is greater than 1 and less than p"
 
 [68b2a5f7-7755-44c3-97b2-d28d21f014a9]
 description = "private key is random"
@@ -11,6 +18,9 @@ description = "private key is random"
 [b4161d8e-53a1-4241-ae8f-48cc86527f22]
 description = "can calculate public key using private key"
 
+[0d25f8d7-4897-4338-a033-2d3d7a9af688]
+description = "can calculate public key when given a different private key"
+
 [cd02ad45-3f52-4510-99cc-5161dad948a8]
 description = "can calculate secret using other party's public key"
 
diff --git a/exercises/practice/diffie-hellman/diffie_hellman_test.py b/exercises/practice/diffie-hellman/diffie_hellman_test.py
index 975c57f9..0326edca 100644
--- a/exercises/practice/diffie-hellman/diffie_hellman_test.py
+++ b/exercises/practice/diffie-hellman/diffie_hellman_test.py
@@ -37,6 +37,19 @@ class DiffieHellmanTest(unittest.TestCase):
             ),
         )
 
+    def test_can_calculate_public_key_when_given_a_different_private_key(self):
+        p = 23
+        g = 5
+        private_key = 15
+        self.assertEqual(
+            19,
+            public_key(
+                p,
+                g,
+                private_key,
+            ),
+        )
+
     def test_can_calculate_secret_using_other_party_s_public_key(self):
         p = 23
         their_public_key = 19
@@ -60,7 +73,3 @@ class DiffieHellmanTest(unittest.TestCase):
         secret_a = secret(p, bob_public_key, alice_private_key)
         secret_b = secret(p, alice_public_key, bob_private_key)
         self.assertTrue(secret_a == secret_b)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml
index f5149e20..6032eedf 100644
--- a/exercises/practice/forth/.meta/tests.toml
+++ b/exercises/practice/forth/.meta/tests.toml
@@ -1,141 +1,151 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [9962203f-f00a-4a85-b404-8a8ecbcec09d]
-description = "numbers just get pushed onto the stack"
+description = "parsing and numbers -> numbers just get pushed onto the stack"
 
 [9e69588e-a3d8-41a3-a371-ea02206c1e6e]
-description = "can add two numbers"
+description = "addition -> can add two numbers"
 
 [52336dd3-30da-4e5c-8523-bdf9a3427657]
-description = "errors if there is nothing on the stack"
+description = "addition -> errors if there is nothing on the stack"
 
 [06efb9a4-817a-435e-b509-06166993c1b8]
-description = "errors if there is only one value on the stack"
+description = "addition -> errors if there is only one value on the stack"
 
 [09687c99-7bbc-44af-8526-e402f997ccbf]
-description = "can subtract two numbers"
+description = "subtraction -> can subtract two numbers"
 
 [5d63eee2-1f7d-4538-b475-e27682ab8032]
-description = "errors if there is nothing on the stack"
+description = "subtraction -> errors if there is nothing on the stack"
 
 [b3cee1b2-9159-418a-b00d-a1bb3765c23b]
-description = "errors if there is only one value on the stack"
+description = "subtraction -> errors if there is only one value on the stack"
 
 [5df0ceb5-922e-401f-974d-8287427dbf21]
-description = "can multiply two numbers"
+description = "multiplication -> can multiply two numbers"
 
 [9e004339-15ac-4063-8ec1-5720f4e75046]
-description = "errors if there is nothing on the stack"
+description = "multiplication -> errors if there is nothing on the stack"
 
 [8ba4b432-9f94-41e0-8fae-3b3712bd51b3]
-description = "errors if there is only one value on the stack"
+description = "multiplication -> errors if there is only one value on the stack"
 
 [e74c2204-b057-4cff-9aa9-31c7c97a93f5]
-description = "can divide two numbers"
+description = "division -> can divide two numbers"
 
 [54f6711c-4b14-4bb0-98ad-d974a22c4620]
-description = "performs integer division"
+description = "division -> performs integer division"
 
 [a5df3219-29b4-4d2f-b427-81f82f42a3f1]
-description = "errors if dividing by zero"
+description = "division -> errors if dividing by zero"
 
 [1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a]
-description = "errors if there is nothing on the stack"
+description = "division -> errors if there is nothing on the stack"
 
 [d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d]
-description = "errors if there is only one value on the stack"
+description = "division -> errors if there is only one value on the stack"
 
 [ee28d729-6692-4a30-b9be-0d830c52a68c]
-description = "addition and subtraction"
+description = "combined arithmetic -> addition and subtraction"
 
 [40b197da-fa4b-4aca-a50b-f000d19422c1]
-description = "multiplication and division"
+description = "combined arithmetic -> multiplication and division"
 
 [c5758235-6eef-4bf6-ab62-c878e50b9957]
-description = "copies a value on the stack"
+description = "dup -> copies a value on the stack"
 
 [f6889006-5a40-41e7-beb3-43b09e5a22f4]
-description = "copies the top value on the stack"
+description = "dup -> copies the top value on the stack"
 
 [40b7569c-8401-4bd4-a30d-9adf70d11bc4]
-description = "errors if there is nothing on the stack"
+description = "dup -> errors if there is nothing on the stack"
 
 [1971da68-1df2-4569-927a-72bf5bb7263c]
-description = "removes the top value on the stack if it is the only one"
+description = "drop -> removes the top value on the stack if it is the only one"
 
 [8929d9f2-4a78-4e0f-90ad-be1a0f313fd9]
-description = "removes the top value on the stack if it is not the only one"
+description = "drop -> removes the top value on the stack if it is not the only one"
 
 [6dd31873-6dd7-4cb8-9e90-7daa33ba045c]
-description = "errors if there is nothing on the stack"
+description = "drop -> errors if there is nothing on the stack"
 
 [3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3]
-description = "swaps the top two values on the stack if they are the only ones"
+description = "swap -> swaps the top two values on the stack if they are the only ones"
 
 [8ce869d5-a503-44e4-ab55-1da36816ff1c]
-description = "swaps the top two values on the stack if they are not the only ones"
+description = "swap -> swaps the top two values on the stack if they are not the only ones"
 
 [74ba5b2a-b028-4759-9176-c5c0e7b2b154]
-description = "errors if there is nothing on the stack"
+description = "swap -> errors if there is nothing on the stack"
 
 [dd52e154-5d0d-4a5c-9e5d-73eb36052bc8]
-description = "errors if there is only one value on the stack"
+description = "swap -> errors if there is only one value on the stack"
 
 [a2654074-ba68-4f93-b014-6b12693a8b50]
-description = "copies the second element if there are only two"
+description = "over -> copies the second element if there are only two"
 
 [c5b51097-741a-4da7-8736-5c93fa856339]
-description = "copies the second element if there are more than two"
+description = "over -> copies the second element if there are more than two"
 
 [6e1703a6-5963-4a03-abba-02e77e3181fd]
-description = "errors if there is nothing on the stack"
+description = "over -> errors if there is nothing on the stack"
 
 [ee574dc4-ef71-46f6-8c6a-b4af3a10c45f]
-description = "errors if there is only one value on the stack"
+description = "over -> errors if there is only one value on the stack"
 
 [ed45cbbf-4dbf-4901-825b-54b20dbee53b]
-description = "can consist of built-in words"
+description = "user-defined words -> can consist of built-in words"
 
 [2726ea44-73e4-436b-bc2b-5ff0c6aa014b]
-description = "execute in the right order"
+description = "user-defined words -> execute in the right order"
 
 [9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33]
-description = "can override other user-defined words"
+description = "user-defined words -> can override other user-defined words"
 
 [669db3f3-5bd6-4be0-83d1-618cd6e4984b]
-description = "can override built-in words"
+description = "user-defined words -> can override built-in words"
 
 [588de2f0-c56e-4c68-be0b-0bb1e603c500]
-description = "can override built-in operators"
+description = "user-defined words -> can override built-in operators"
 
 [ac12aaaf-26c6-4a10-8b3c-1c958fa2914c]
-description = "can use different words with the same name"
+description = "user-defined words -> can use different words with the same name"
 
 [53f82ef0-2750-4ccb-ac04-5d8c1aefabb1]
-description = "can define word that uses word with the same name"
+description = "user-defined words -> can define word that uses word with the same name"
 
 [35958cee-a976-4a0f-9378-f678518fa322]
-description = "cannot redefine non-negative numbers"
+description = "user-defined words -> cannot redefine non-negative numbers"
+
+[df5b2815-3843-4f55-b16c-c3ed507292a7]
+description = "user-defined words -> cannot redefine negative numbers"
 
 [5180f261-89dd-491e-b230-62737e09806f]
-description = "errors if executing a non-existent word"
+description = "user-defined words -> errors if executing a non-existent word"
 
 [7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6]
-description = "DUP is case-insensitive"
+description = "case-insensitivity -> DUP is case-insensitive"
 
 [339ed30b-f5b4-47ff-ab1c-67591a9cd336]
-description = "DROP is case-insensitive"
+description = "case-insensitivity -> DROP is case-insensitive"
 
 [ee1af31e-1355-4b1b-bb95-f9d0b2961b87]
-description = "SWAP is case-insensitive"
+description = "case-insensitivity -> SWAP is case-insensitive"
 
 [acdc3a49-14c8-4cc2-945d-11edee6408fa]
-description = "OVER is case-insensitive"
+description = "case-insensitivity -> OVER is case-insensitive"
 
 [5934454f-a24f-4efc-9fdd-5794e5f0c23c]
-description = "user-defined words are case-insensitive"
+description = "case-insensitivity -> user-defined words are case-insensitive"
 
 [037d4299-195f-4be7-a46d-f07ca6280a06]
-description = "definitions are case-insensitive"
+description = "case-insensitivity -> definitions are case-insensitive"
diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py
index 6eabfb8f..f8a1e602 100644
--- a/exercises/practice/forth/forth_test.py
+++ b/exercises/practice/forth/forth_test.py
@@ -209,6 +209,12 @@ class ForthTest(unittest.TestCase):
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(str(err.exception.args[0]), "illegal operation")
 
+    def test_user_defined_words_cannot_redefine_negative_numbers(self):
+        with self.assertRaises(ValueError) as err:
+            evaluate([": -1 2 ;"])
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(str(err.exception.args[0]), "illegal operation")
+
     def test_user_defined_words_errors_if_executing_a_non_existent_word(self):
         with self.assertRaises(ValueError) as err:
             evaluate(["foo"])
diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml
index eada129d..d774e9e3 100644
--- a/exercises/practice/grade-school/.meta/tests.toml
+++ b/exercises/practice/grade-school/.meta/tests.toml
@@ -1,24 +1,83 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[a3f0fb58-f240-4723-8ddc-e644666b85cc]
+description = "Roster is empty when no student is added"
+
+[9337267f-7793-4b90-9b4a-8e3978408824]
+description = "Add a student"
 
 [6d0a30e4-1b4e-472e-8e20-c41702125667]
-description = "Adding a student adds them to the sorted roster"
+description = "Student is added to the roster"
+
+[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a]
+description = "Adding multiple students in the same grade in the roster"
 
 [233be705-dd58-4968-889d-fb3c7954c9cc]
-description = "Adding more student adds them to the sorted roster"
+description = "Multiple students in the same grade are added to the roster"
+
+[87c871c1-6bde-4413-9c44-73d59a259d83]
+description = "Cannot add student to same grade in the roster more than once"
+
+[c125dab7-2a53-492f-a99a-56ad511940d8]
+description = "A student can't be in two different grades"
+
+[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1]
+description = "A student can only be added to the same grade in the roster once"
+reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8"
+
+[d7982c4f-1602-49f6-a651-620f2614243a]
+description = "Student not added to same grade in the roster more than once"
+reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1"
+
+[e70d5d8f-43a9-41fd-94a4-1ea0fa338056]
+description = "Adding students in multiple grades"
 
 [75a51579-d1d7-407c-a2f8-2166e984e8ab]
-description = "Adding students to different grades adds them to the same sorted roster"
+description = "Students in multiple grades are added to the roster"
 
-[a3f0fb58-f240-4723-8ddc-e644666b85cc]
-description = "Roster returns an empty list if there are no students enrolled"
+[7df542f1-57ce-433c-b249-ff77028ec479]
+description = "Cannot add same student to multiple grades in the roster"
 
-[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6]
-description = "Student names with grades are displayed in the same sorted roster"
+[6a03b61e-1211-4783-a3cc-fc7f773fba3f]
+description = "A student cannot be added to more than one grade in the sorted roster"
+reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8"
 
-[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e]
-description = "Grade returns the students in that grade in alphabetical order"
+[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5]
+description = "Student not added to multiple grades in the roster"
+reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f"
+
+[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6]
+description = "Students are sorted by grades in the roster"
+
+[d9fb5bea-f5aa-4524-9d61-c158d8906807]
+description = "Students are sorted by name in the roster"
+
+[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6]
+description = "Students are sorted by grades and then by name in the roster"
 
 [5e67aa3c-a3c6-4407-a183-d8fe59cd1630]
-description = "Grade returns an empty list if there are no students in that grade"
+description = "Grade is empty if no students in the roster"
+
+[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6]
+description = "Grade is empty if no students in that grade"
+
+[2bfc697c-adf2-4b65-8d0f-c46e085f796e]
+description = "Student not added to same grade more than once"
+
+[66c8e141-68ab-4a04-a15a-c28bc07fe6b9]
+description = "Student not added to multiple grades"
+
+[c9c1fc2f-42e0-4d2c-b361-99271f03eda7]
+description = "Student not added to other grade for multiple grades"
+
+[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e]
+description = "Students are sorted by name in a grade"
diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py
index f1643de2..2ef26d2e 100644
--- a/exercises/practice/grade-school/grade_school_test.py
+++ b/exercises/practice/grade-school/grade_school_test.py
@@ -11,27 +11,133 @@ class GradeSchoolTest(unittest.TestCase):
     def test_roster_is_empty_when_no_student_is_added(self):
         school = School()
         expected = []
+
         self.assertEqual(school.roster(), expected)
 
+    def test_add_a_student(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        expected = [True]
+        self.assertEqual(school.added(), expected)
+
     def test_student_is_added_to_the_roster(self):
         school = School()
         school.add_student(name="Aimee", grade=2)
         expected = ["Aimee"]
+
         self.assertEqual(school.roster(), expected)
 
+    def test_adding_multiple_students_in_the_same_grade_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = [True, True, True]
+        self.assertEqual(school.added(), expected)
+
     def test_multiple_students_in_the_same_grade_are_added_to_the_roster(self):
         school = School()
         school.add_student(name="Blair", grade=2)
         school.add_student(name="James", grade=2)
         school.add_student(name="Paul", grade=2)
         expected = ["Blair", "James", "Paul"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_cannot_add_student_to_same_grade_in_the_roster_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = [True, True, False, True]
+        self.assertEqual(school.added(), expected)
+
+    def test_a_student_can_t_be_in_two_different_grades(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=1)
+        expected = []
+        self.assertEqual(school.roster(2), expected)
+
+    def test_a_student_can_only_be_added_to_the_same_grade_in_the_roster_once(self):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=2)
+        expected = ["Aimee"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_student_not_added_to_same_grade_in_the_roster_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = ["Blair", "James", "Paul"]
+
         self.assertEqual(school.roster(), expected)
 
+    def test_adding_students_in_multiple_grades(self):
+        school = School()
+        school.add_student(name="Chelsea", grade=3)
+        school.add_student(name="Logan", grade=7)
+        expected = [True, True]
+        self.assertEqual(school.added(), expected)
+
     def test_students_in_multiple_grades_are_added_to_the_roster(self):
         school = School()
         school.add_student(name="Chelsea", grade=3)
         school.add_student(name="Logan", grade=7)
         expected = ["Chelsea", "Logan"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_cannot_add_same_student_to_multiple_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = [True, True, False, True]
+        self.assertEqual(school.added(), expected)
+
+    def test_a_student_cannot_be_added_to_more_than_one_grade_in_the_sorted_roster(
+        self,
+    ):
+        school = School()
+        school.add_student(name="Aimee", grade=2)
+        school.add_student(name="Aimee", grade=1)
+        expected = ["Aimee"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_student_not_added_to_multiple_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Blair", "James", "Paul"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_students_are_sorted_by_grades_in_the_roster(self):
+        school = School()
+        school.add_student(name="Jim", grade=3)
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Anna", grade=1)
+        expected = ["Anna", "Peter", "Jim"]
+
+        self.assertEqual(school.roster(), expected)
+
+    def test_students_are_sorted_by_name_in_the_roster(self):
+        school = School()
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Zoe", grade=2)
+        school.add_student(name="Alex", grade=2)
+        expected = ["Alex", "Peter", "Zoe"]
+
         self.assertEqual(school.roster(), expected)
 
     def test_students_are_sorted_by_grades_and_then_by_name_in_the_roster(self):
@@ -44,6 +150,7 @@ class GradeSchoolTest(unittest.TestCase):
         school.add_student(name="Jim", grade=3)
         school.add_student(name="Charlie", grade=1)
         expected = ["Anna", "Barb", "Charlie", "Alex", "Peter", "Zoe", "Jim"]
+
         self.assertEqual(school.roster(), expected)
 
     def test_grade_is_empty_if_no_students_in_the_roster(self):
@@ -51,6 +158,42 @@ class GradeSchoolTest(unittest.TestCase):
         expected = []
         self.assertEqual(school.grade(1), expected)
 
+    def test_grade_is_empty_if_no_students_in_that_grade(self):
+        school = School()
+        school.add_student(name="Peter", grade=2)
+        school.add_student(name="Zoe", grade=2)
+        school.add_student(name="Alex", grade=2)
+        school.add_student(name="Jim", grade=3)
+        expected = []
+        self.assertEqual(school.grade(1), expected)
+
+    def test_student_not_added_to_same_grade_more_than_once(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="Paul", grade=2)
+        expected = ["Blair", "James", "Paul"]
+        self.assertEqual(school.grade(2), expected)
+
+    def test_student_not_added_to_multiple_grades(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Blair", "James"]
+        self.assertEqual(school.grade(2), expected)
+
+    def test_student_not_added_to_other_grade_for_multiple_grades(self):
+        school = School()
+        school.add_student(name="Blair", grade=2)
+        school.add_student(name="James", grade=2)
+        school.add_student(name="James", grade=3)
+        school.add_student(name="Paul", grade=3)
+        expected = ["Paul"]
+        self.assertEqual(school.grade(3), expected)
+
     def test_students_are_sorted_by_name_in_a_grade(self):
         school = School()
         school.add_student(name="Franklin", grade=5)
@@ -58,7 +201,3 @@ class GradeSchoolTest(unittest.TestCase):
         school.add_student(name="Jeff", grade=1)
         expected = ["Bradley", "Franklin"]
         self.assertEqual(school.grade(5), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/hamming/.meta/tests.toml b/exercises/practice/hamming/.meta/tests.toml
index b2f80f48..0caf456c 100644
--- a/exercises/practice/hamming/.meta/tests.toml
+++ b/exercises/practice/hamming/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [f6dcb64f-03b0-4b60-81b1-3c9dbf47e887]
 description = "empty strands"
@@ -20,11 +27,35 @@ description = "long different strands"
 [919f8ef0-b767-4d1b-8516-6379d07fcb28]
 description = "disallow first strand longer"
 
+[b9228bb1-465f-4141-b40f-1f99812de5a8]
+description = "disallow first strand longer"
+reimplements = "919f8ef0-b767-4d1b-8516-6379d07fcb28"
+
 [8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e]
 description = "disallow second strand longer"
 
+[dab38838-26bb-4fff-acbe-3b0a9bfeba2d]
+description = "disallow second strand longer"
+reimplements = "8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e"
+
 [5dce058b-28d4-4ca7-aa64-adfe4e17784c]
 description = "disallow left empty strand"
 
+[db92e77e-7c72-499d-8fe6-9354d2bfd504]
+description = "disallow left empty strand"
+reimplements = "5dce058b-28d4-4ca7-aa64-adfe4e17784c"
+
+[b764d47c-83ff-4de2-ab10-6cfe4b15c0f3]
+description = "disallow empty first strand"
+reimplements = "db92e77e-7c72-499d-8fe6-9354d2bfd504"
+
 [38826d4b-16fb-4639-ac3e-ba027dec8b5f]
 description = "disallow right empty strand"
+
+[920cd6e3-18f4-4143-b6b8-74270bb8f8a3]
+description = "disallow right empty strand"
+reimplements = "38826d4b-16fb-4639-ac3e-ba027dec8b5f"
+
+[9ab9262f-3521-4191-81f5-0ed184a5aa89]
+description = "disallow empty second strand"
+reimplements = "920cd6e3-18f4-4143-b6b8-74270bb8f8a3"
diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py
index 1210f0d6..a0fa465b 100644
--- a/exercises/practice/hamming/hamming_test.py
+++ b/exercises/practice/hamming/hamming_test.py
@@ -30,6 +30,20 @@ class HammingTest(unittest.TestCase):
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
 
+    def test_disallow_first_strand_longer(self):
+        with self.assertRaises(ValueError) as err:
+            distance("AATG", "AAA")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_second_strand_longer(self):
+        with self.assertRaises(ValueError) as err:
+            distance("ATA", "AGTG")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
     def test_disallow_second_strand_longer(self):
         with self.assertRaises(ValueError) as err:
             distance("ATA", "AGTG")
@@ -44,9 +58,37 @@ class HammingTest(unittest.TestCase):
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
 
+    def test_disallow_left_empty_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("", "G")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_empty_first_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("", "G")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_right_empty_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("G", "")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
     def test_disallow_right_empty_strand(self):
         with self.assertRaises(ValueError) as err:
             distance("G", "")
 
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
+
+    def test_disallow_empty_second_strand(self):
+        with self.assertRaises(ValueError) as err:
+            distance("G", "")
+
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Strands must be of equal length.")
diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml
index 4008e012..c81c2288 100644
--- a/exercises/practice/high-scores/.meta/tests.toml
+++ b/exercises/practice/high-scores/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [1035eb93-2208-4c22-bab8-fef06769a73c]
 description = "List of scores"
@@ -12,16 +19,22 @@ description = "Latest score"
 description = "Personal best"
 
 [3d996a97-c81c-4642-9afc-80b80dc14015]
-description = "Personal top three from a list of scores"
+description = "Top 3 scores -> Personal top three from a list of scores"
 
 [1084ecb5-3eb4-46fe-a816-e40331a4e83a]
-description = "Personal top highest to lowest"
+description = "Top 3 scores -> Personal top highest to lowest"
 
 [e6465b6b-5a11-4936-bfe3-35241c4f4f16]
-description = "Personal top when there is a tie"
+description = "Top 3 scores -> Personal top when there is a tie"
 
 [f73b02af-c8fd-41c9-91b9-c86eaa86bce2]
-description = "Personal top when there are less than 3"
+description = "Top 3 scores -> Personal top when there are less than 3"
 
 [16608eae-f60f-4a88-800e-aabce5df2865]
-description = "Personal top when there is only one"
+description = "Top 3 scores -> Personal top when there is only one"
+
+[2df075f9-fec9-4756-8f40-98c52a11504f]
+description = "Top 3 scores -> Latest score after personal top scores"
+
+[809c4058-7eb1-4206-b01e-79238b9b71bc]
+description = "Top 3 scores -> Scores after personal top scores"
diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py
index 04b788a1..629d1bdb 100644
--- a/exercises/practice/high-scores/high_scores_test.py
+++ b/exercises/practice/high-scores/high_scores_test.py
@@ -2,8 +2,10 @@ import unittest
 
 from high_scores import (
     latest,
+    latest_after_top_three,
     personal_best,
     personal_top_three,
+    scores_after_top_three,
 )
 
 # Tests adapted from `problem-specifications//canonical-data.json`
@@ -45,6 +47,12 @@ class HighScoresTest(unittest.TestCase):
         expected = [40]
         self.assertEqual(personal_top_three(scores), expected)
 
+    def test_latest_score_after_personal_top_scores(self):
+        scores = [70, 50, 20, 30]
+        expected = 30
+        self.assertEqual(latest_after_top_three(scores), expected)
 
-if __name__ == "__main__":
-    unittest.main()
+    def test_scores_after_personal_top_scores(self):
+        scores = [30, 50, 20, 70]
+        expected = [30, 50, 20, 70]
+        self.assertEqual(scores_after_top_three(scores), expected)
diff --git a/exercises/practice/isogram/.meta/tests.toml b/exercises/practice/isogram/.meta/tests.toml
index 7187c340..ba04c664 100644
--- a/exercises/practice/isogram/.meta/tests.toml
+++ b/exercises/practice/isogram/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [a0e97d2d-669e-47c7-8134-518a1e2c4555]
 description = "empty string"
@@ -40,3 +47,6 @@ description = "duplicated character in the middle"
 
 [310ac53d-8932-47bc-bbb4-b2b94f25a83e]
 description = "same first and last characters"
+
+[0d0b8644-0a1e-4a31-a432-2b3ee270d847]
+description = "word with duplicated character and with two hyphens"
diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py
index 11aca026..8d00d2b5 100644
--- a/exercises/practice/isogram/isogram_test.py
+++ b/exercises/practice/isogram/isogram_test.py
@@ -47,6 +47,5 @@ class IsogramTest(unittest.TestCase):
     def test_same_first_and_last_characters(self):
         self.assertIs(is_isogram("angola"), False)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_word_with_duplicated_character_and_with_two_hyphens(self):
+        self.assertIs(is_isogram("up-to-date"), False)
diff --git a/exercises/practice/kindergarten-garden/.meta/tests.toml b/exercises/practice/kindergarten-garden/.meta/tests.toml
index 1778a615..0cdd9ad6 100644
--- a/exercises/practice/kindergarten-garden/.meta/tests.toml
+++ b/exercises/practice/kindergarten-garden/.meta/tests.toml
@@ -1,30 +1,61 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [1fc316ed-17ab-4fba-88ef-3ae78296b692]
-description = "garden with single student"
+description = "partial garden -> garden with single student"
 
 [acd19dc1-2200-4317-bc2a-08f021276b40]
-description = "different garden with single student"
+description = "partial garden -> different garden with single student"
 
 [c376fcc8-349c-446c-94b0-903947315757]
-description = "garden with two students"
+description = "partial garden -> garden with two students"
 
 [2d620f45-9617-4924-9d27-751c80d17db9]
-description = "second student's garden"
+description = "partial garden -> multiple students for the same garden with three students -> second student's garden"
 
 [57712331-4896-4364-89f8-576421d69c44]
-description = "third student's garden"
+description = "partial garden -> multiple students for the same garden with three students -> third student's garden"
 
 [149b4290-58e1-40f2-8ae4-8b87c46e765b]
-description = "first student's garden"
+description = "full garden -> for Alice, first student's garden"
 
 [ba25dbbc-10bd-4a37-b18e-f89ecd098a5e]
-description = "second student's garden"
+description = "full garden -> for Bob, second student's garden"
+
+[566b621b-f18e-4c5f-873e-be30544b838c]
+description = "full garden -> for Charlie"
+
+[3ad3df57-dd98-46fc-9269-1877abf612aa]
+description = "full garden -> for David"
+
+[0f0a55d1-9710-46ed-a0eb-399ba8c72db2]
+description = "full garden -> for Eve"
+
+[a7e80c90-b140-4ea1-aee3-f4625365c9a4]
+description = "full garden -> for Fred"
+
+[9d94b273-2933-471b-86e8-dba68694c615]
+description = "full garden -> for Ginny"
+
+[f55bc6c2-ade8-4844-87c4-87196f1b7258]
+description = "full garden -> for Harriet"
+
+[759070a3-1bb1-4dd4-be2c-7cce1d7679ae]
+description = "full garden -> for Ileana"
+
+[78578123-2755-4d4a-9c7d-e985b8dda1c6]
+description = "full garden -> for Joseph"
 
 [6bb66df7-f433-41ab-aec2-3ead6e99f65b]
-description = "second to last student's garden"
+description = "full garden -> for Kincaid, second to last student's garden"
 
 [d7edec11-6488-418a-94e6-ed509e0fa7eb]
-description = "last student's garden"
+description = "full garden -> for Larry, last student's garden"
diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
index 6b4ba358..95095cab 100644
--- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
+++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
@@ -44,6 +44,50 @@ class KindergartenGardenTest(unittest.TestCase):
         garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
         self.assertEqual(garden.plants("Bob"), ["Clover", "Grass", "Clover", "Clover"])
 
+    def test_full_garden_for_charlie(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Charlie"), ["Violets", "Violets", "Clover", "Grass"]
+        )
+
+    def test_full_garden_for_david(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("David"), ["Radishes", "Violets", "Clover", "Radishes"]
+        )
+
+    def test_full_garden_for_eve(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(garden.plants("Eve"), ["Clover", "Grass", "Radishes", "Grass"])
+
+    def test_full_garden_for_fred(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Fred"), ["Grass", "Clover", "Violets", "Clover"]
+        )
+
+    def test_full_garden_for_ginny(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(garden.plants("Ginny"), ["Clover", "Grass", "Grass", "Clover"])
+
+    def test_full_garden_for_harriet(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Harriet"), ["Violets", "Radishes", "Radishes", "Violets"]
+        )
+
+    def test_full_garden_for_ileana(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Ileana"), ["Grass", "Clover", "Violets", "Clover"]
+        )
+
+    def test_full_garden_for_joseph(self):
+        garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
+        self.assertEqual(
+            garden.plants("Joseph"), ["Violets", "Clover", "Violets", "Grass"]
+        )
+
     def test_full_garden_for_kincaid_second_to_last_student_s_garden(self):
         garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
         self.assertEqual(
@@ -73,7 +117,3 @@ class KindergartenGardenTest(unittest.TestCase):
         self.assertEqual(
             garden.plants("Xander"), ["Radishes", "Grass", "Clover", "Violets"]
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml
index 9bb82241..eea08d79 100644
--- a/exercises/practice/luhn/.meta/tests.toml
+++ b/exercises/practice/luhn/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [792a7082-feb7-48c7-b88b-bbfec160865e]
 description = "single digit strings can not be valid"
@@ -50,8 +57,17 @@ description = "more than a single zero is valid"
 [ab56fa80-5de8-4735-8a4a-14dae588663e]
 description = "input digit 9 is correctly converted to output digit 9"
 
+[b9887ee8-8337-46c5-bc45-3bcab51bc36f]
+description = "very long input is valid"
+
+[8a7c0e24-85ea-4154-9cf1-c2db90eabc08]
+description = "valid luhn with an odd number of digits and non zero first digit"
+
 [39a06a5a-5bad-4e0f-b215-b042d46209b1]
 description = "using ascii value for non-doubled non-digit isn't allowed"
 
 [f94cf191-a62f-4868-bc72-7253114aa157]
 description = "using ascii value for doubled non-digit isn't allowed"
+
+[8b72ad26-c8be-49a2-b99c-bcc3bf631b33]
+description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed"
diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py
index b0ee748f..a60d8525 100644
--- a/exercises/practice/luhn/luhn_test.py
+++ b/exercises/practice/luhn/luhn_test.py
@@ -56,12 +56,23 @@ class LuhnTest(unittest.TestCase):
     def test_input_digit_9_is_correctly_converted_to_output_digit_9(self):
         self.assertIs(Luhn("091").valid(), True)
 
+    def test_very_long_input_is_valid(self):
+        self.assertIs(Luhn("9999999999 9999999999 9999999999 9999999999").valid(), True)
+
+    def test_valid_luhn_with_an_odd_number_of_digits_and_non_zero_first_digit(self):
+        self.assertIs(Luhn("109").valid(), True)
+
     def test_using_ascii_value_for_non_doubled_non_digit_isn_t_allowed(self):
         self.assertIs(Luhn("055b 444 285").valid(), False)
 
     def test_using_ascii_value_for_doubled_non_digit_isn_t_allowed(self):
         self.assertIs(Luhn(":9").valid(), False)
 
+    def test_non_numeric_non_space_char_in_the_middle_with_a_sum_that_s_divisible_by_10_isn_t_allowed(
+        self,
+    ):
+        self.assertIs(Luhn("59%59").valid(), False)
+
     # Additional tests for this track
 
     def test_is_valid_can_be_called_repeatedly(self):
@@ -71,7 +82,3 @@ class LuhnTest(unittest.TestCase):
         number = Luhn("055 444 285")
         self.assertIs(number.valid(), True)
         self.assertIs(number.valid(), True)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/markdown/.meta/tests.toml b/exercises/practice/markdown/.meta/tests.toml
index fecbcf50..28b7baa7 100644
--- a/exercises/practice/markdown/.meta/tests.toml
+++ b/exercises/practice/markdown/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [e75c8103-a6b8-45d9-84ad-e68520545f6e]
 description = "parses normal text as a paragraph"
@@ -20,9 +27,26 @@ description = "with h1 header level"
 [d0f7a31f-6935-44ac-8a9a-1e8ab16af77f]
 description = "with h2 header level"
 
+[9df3f500-0622-4696-81a7-d5babd9b5f49]
+description = "with h3 header level"
+
+[50862777-a5e8-42e9-a3b8-4ba6fcd0ed03]
+description = "with h4 header level"
+
+[ee1c23ac-4c86-4f2a-8b9c-403548d4ab82]
+description = "with h5 header level"
+
 [13b5f410-33f5-44f0-a6a7-cfd4ab74b5d5]
 description = "with h6 header level"
 
+[6dca5d10-5c22-4e2a-ac2b-bd6f21e61939]
+description = "with h7 header level"
+include = false
+
+[81c0c4db-435e-4d77-860d-45afacdad810]
+description = "h7 header level is a paragraph"
+reimplements = "6dca5d10-5c22-4e2a-ac2b-bd6f21e61939"
+
 [25288a2b-8edc-45db-84cf-0b6c6ee034d6]
 description = "unordered lists"
 
diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py
index 5f18a6ba..5c059c72 100644
--- a/exercises/practice/markdown/markdown_test.py
+++ b/exercises/practice/markdown/markdown_test.py
@@ -35,11 +35,30 @@ class MarkdownTest(unittest.TestCase):
     def test_with_h2_header_level(self):
         self.assertEqual(parse("## This will be an h2"), "<h2>This will be an h2</h2>")
 
+    def test_with_h3_header_level(self):
+        self.assertEqual(parse("### This will be an h3"), "<h3>This will be an h3</h3>")
+
+    def test_with_h4_header_level(self):
+        self.assertEqual(
+            parse("#### This will be an h4"), "<h4>This will be an h4</h4>"
+        )
+
+    def test_with_h5_header_level(self):
+        self.assertEqual(
+            parse("##### This will be an h5"), "<h5>This will be an h5</h5>"
+        )
+
     def test_with_h6_header_level(self):
         self.assertEqual(
             parse("###### This will be an h6"), "<h6>This will be an h6</h6>"
         )
 
+    def test_h7_header_level_is_a_paragraph(self):
+        self.assertEqual(
+            parse("####### This will not be an h7"),
+            "<p>####### This will not be an h7</p>",
+        )
+
     def test_unordered_lists(self):
         self.assertEqual(
             parse("* Item 1\n* Item 2"), "<ul><li>Item 1</li><li>Item 2</li></ul>"
@@ -80,7 +99,3 @@ class MarkdownTest(unittest.TestCase):
             parse("# Start a list\n* Item 1\n* Item 2\nEnd a list"),
             "<h1>Start a list</h1><ul><li>Item 1</li><li>Item 2</li></ul><p>End a list</p>",
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/prime-factors/.meta/tests.toml b/exercises/practice/prime-factors/.meta/tests.toml
index f3f05a3e..6f9cc8ce 100644
--- a/exercises/practice/prime-factors/.meta/tests.toml
+++ b/exercises/practice/prime-factors/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [924fc966-a8f5-4288-82f2-6b9224819ccd]
 description = "no factors"
@@ -8,12 +15,27 @@ description = "no factors"
 [17e30670-b105-4305-af53-ddde182cb6ad]
 description = "prime number"
 
+[238d57c8-4c12-42ef-af34-ae4929f94789]
+description = "another prime number"
+
 [f59b8350-a180-495a-8fb1-1712fbee1158]
 description = "square of a prime"
 
+[756949d3-3158-4e3d-91f2-c4f9f043ee70]
+description = "product of first prime"
+
 [bc8c113f-9580-4516-8669-c5fc29512ceb]
 description = "cube of a prime"
 
+[7d6a3300-a4cb-4065-bd33-0ced1de6cb44]
+description = "product of second prime"
+
+[073ac0b2-c915-4362-929d-fc45f7b9a9e4]
+description = "product of third prime"
+
+[6e0e4912-7fb6-47f3-a9ad-dbcd79340c75]
+description = "product of first and second prime"
+
 [00485cd3-a3fe-4fbe-a64a-a4308fc1f870]
 description = "product of primes and non-primes"
 
diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py
index 80deb17c..02b36e7a 100644
--- a/exercises/practice/prime-factors/prime_factors_test.py
+++ b/exercises/practice/prime-factors/prime_factors_test.py
@@ -14,12 +14,27 @@ class PrimeFactorsTest(unittest.TestCase):
     def test_prime_number(self):
         self.assertEqual(factors(2), [2])
 
+    def test_another_prime_number(self):
+        self.assertEqual(factors(3), [3])
+
     def test_square_of_a_prime(self):
         self.assertEqual(factors(9), [3, 3])
 
+    def test_product_of_first_prime(self):
+        self.assertEqual(factors(4), [2, 2])
+
     def test_cube_of_a_prime(self):
         self.assertEqual(factors(8), [2, 2, 2])
 
+    def test_product_of_second_prime(self):
+        self.assertEqual(factors(27), [3, 3, 3])
+
+    def test_product_of_third_prime(self):
+        self.assertEqual(factors(625), [5, 5, 5, 5])
+
+    def test_product_of_first_and_second_prime(self):
+        self.assertEqual(factors(6), [2, 3])
+
     def test_product_of_primes_and_non_primes(self):
         self.assertEqual(factors(12), [2, 2, 3])
 
@@ -28,7 +43,3 @@ class PrimeFactorsTest(unittest.TestCase):
 
     def test_factors_include_a_large_prime(self):
         self.assertEqual(factors(93819012551), [11, 9539, 894119])
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rational-numbers/.meta/tests.toml b/exercises/practice/rational-numbers/.meta/tests.toml
index 2cf56c08..30cf65de 100644
--- a/exercises/practice/rational-numbers/.meta/tests.toml
+++ b/exercises/practice/rational-numbers/.meta/tests.toml
@@ -1,117 +1,127 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [0ba4d988-044c-4ed5-9215-4d0bb8d0ae9f]
-description = "Add two positive rational numbers"
+description = "Arithmetic -> Addition -> Add two positive rational numbers"
 
 [88ebc342-a2ac-4812-a656-7b664f718b6a]
-description = "Add a positive rational number and a negative rational number"
+description = "Arithmetic -> Addition -> Add a positive rational number and a negative rational number"
 
 [92ed09c2-991e-4082-a602-13557080205c]
-description = "Add two negative rational numbers"
+description = "Arithmetic -> Addition -> Add two negative rational numbers"
 
 [6e58999e-3350-45fb-a104-aac7f4a9dd11]
-description = "Add a rational number to its additive inverse"
+description = "Arithmetic -> Addition -> Add a rational number to its additive inverse"
 
 [47bba350-9db1-4ab9-b412-4a7e1f72a66e]
-description = "Subtract two positive rational numbers"
+description = "Arithmetic -> Subtraction -> Subtract two positive rational numbers"
 
 [93926e2a-3e82-4aee-98a7-fc33fb328e87]
-description = "Subtract a positive rational number and a negative rational number"
+description = "Arithmetic -> Subtraction -> Subtract a positive rational number and a negative rational number"
 
 [a965ba45-9b26-442b-bdc7-7728e4b8d4cc]
-description = "Subtract two negative rational numbers"
+description = "Arithmetic -> Subtraction -> Subtract two negative rational numbers"
 
 [0df0e003-f68e-4209-8c6e-6a4e76af5058]
-description = "Subtract a rational number from itself"
+description = "Arithmetic -> Subtraction -> Subtract a rational number from itself"
 
 [34fde77a-75f4-4204-8050-8d3a937958d3]
-description = "Multiply two positive rational numbers"
+description = "Arithmetic -> Multiplication -> Multiply two positive rational numbers"
 
 [6d015cf0-0ea3-41f1-93de-0b8e38e88bae]
-description = "Multiply a negative rational number by a positive rational number"
+description = "Arithmetic -> Multiplication -> Multiply a negative rational number by a positive rational number"
 
 [d1bf1b55-954e-41b1-8c92-9fc6beeb76fa]
-description = "Multiply two negative rational numbers"
+description = "Arithmetic -> Multiplication -> Multiply two negative rational numbers"
 
 [a9b8f529-9ec7-4c79-a517-19365d779040]
-description = "Multiply a rational number by its reciprocal"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by its reciprocal"
 
 [d89d6429-22fa-4368-ab04-9e01a44d3b48]
-description = "Multiply a rational number by 1"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by 1"
 
 [0d95c8b9-1482-4ed7-bac9-b8694fa90145]
-description = "Multiply a rational number by 0"
+description = "Arithmetic -> Multiplication -> Multiply a rational number by 0"
 
 [1de088f4-64be-4e6e-93fd-5997ae7c9798]
-description = "Divide two positive rational numbers"
+description = "Arithmetic -> Division -> Divide two positive rational numbers"
 
 [7d7983db-652a-4e66-981a-e921fb38d9a9]
-description = "Divide a positive rational number by a negative rational number"
+description = "Arithmetic -> Division -> Divide a positive rational number by a negative rational number"
 
 [1b434d1b-5b38-4cee-aaf5-b9495c399e34]
-description = "Divide two negative rational numbers"
+description = "Arithmetic -> Division -> Divide two negative rational numbers"
 
 [d81c2ebf-3612-45a6-b4e0-f0d47812bd59]
-description = "Divide a rational number by 1"
+description = "Arithmetic -> Division -> Divide a rational number by 1"
 
 [5fee0d8e-5955-4324-acbe-54cdca94ddaa]
-description = "Absolute value of a positive rational number"
+description = "Absolute value -> Absolute value of a positive rational number"
 
 [3cb570b6-c36a-4963-a380-c0834321bcaa]
-description = "Absolute value of a positive rational number with negative numerator and denominator"
+description = "Absolute value -> Absolute value of a positive rational number with negative numerator and denominator"
 
 [6a05f9a0-1f6b-470b-8ff7-41af81773f25]
-description = "Absolute value of a negative rational number"
+description = "Absolute value -> Absolute value of a negative rational number"
 
 [5d0f2336-3694-464f-8df9-f5852fda99dd]
-description = "Absolute value of a negative rational number with negative denominator"
+description = "Absolute value -> Absolute value of a negative rational number with negative denominator"
 
 [f8e1ed4b-9dca-47fb-a01e-5311457b3118]
-description = "Absolute value of zero"
+description = "Absolute value -> Absolute value of zero"
 
 [ea2ad2af-3dab-41e7-bb9f-bd6819668a84]
-description = "Raise a positive rational number to a positive integer power"
+description = "Exponentiation of a rational number -> Raise a positive rational number to a positive integer power"
 
 [8168edd2-0af3-45b1-b03f-72c01332e10a]
-description = "Raise a negative rational number to a positive integer power"
+description = "Exponentiation of a rational number -> Raise a negative rational number to a positive integer power"
 
 [e2f25b1d-e4de-4102-abc3-c2bb7c4591e4]
-description = "Raise zero to an integer power"
+description = "Exponentiation of a rational number -> Raise zero to an integer power"
 
 [431cac50-ab8b-4d58-8e73-319d5404b762]
-description = "Raise one to an integer power"
+description = "Exponentiation of a rational number -> Raise one to an integer power"
 
 [7d164739-d68a-4a9c-b99f-dd77ce5d55e6]
-description = "Raise a positive rational number to the power of zero"
+description = "Exponentiation of a rational number -> Raise a positive rational number to the power of zero"
 
 [eb6bd5f5-f880-4bcd-8103-e736cb6e41d1]
-description = "Raise a negative rational number to the power of zero"
+description = "Exponentiation of a rational number -> Raise a negative rational number to the power of zero"
 
 [30b467dd-c158-46f5-9ffb-c106de2fd6fa]
-description = "Raise a real number to a positive rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a positive rational number"
 
 [6e026bcc-be40-4b7b-ae22-eeaafc5a1789]
-description = "Raise a real number to a negative rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a negative rational number"
 
 [9f866da7-e893-407f-8cd2-ee85d496eec5]
-description = "Raise a real number to a zero rational number"
+description = "Exponentiation of a real number to a rational number -> Raise a real number to a zero rational number"
 
 [0a63fbde-b59c-4c26-8237-1e0c73354d0a]
-description = "Reduce a positive rational number to lowest terms"
+description = "Reduction to lowest terms -> Reduce a positive rational number to lowest terms"
+
+[5ed6f248-ad8d-4d4e-a545-9146c6727f33]
+description = "Reduction to lowest terms -> Reduce places the minus sign on the numerator"
 
 [f87c2a4e-d29c-496e-a193-318c503e4402]
-description = "Reduce a negative rational number to lowest terms"
+description = "Reduction to lowest terms -> Reduce a negative rational number to lowest terms"
 
 [3b92ffc0-5b70-4a43-8885-8acee79cdaaf]
-description = "Reduce a rational number with a negative denominator to lowest terms"
+description = "Reduction to lowest terms -> Reduce a rational number with a negative denominator to lowest terms"
 
 [c9dbd2e6-5ac0-4a41-84c1-48b645b4f663]
-description = "Reduce zero to lowest terms"
+description = "Reduction to lowest terms -> Reduce zero to lowest terms"
 
 [297b45ad-2054-4874-84d4-0358dc1b8887]
-description = "Reduce an integer to lowest terms"
+description = "Reduction to lowest terms -> Reduce an integer to lowest terms"
 
 [a73a17fe-fe8c-4a1c-a63b-e7579e333d9e]
-description = "Reduce one to lowest terms"
+description = "Reduction to lowest terms -> Reduce one to lowest terms"
diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py
index f4486bce..323439d0 100644
--- a/exercises/practice/rational-numbers/rational_numbers_test.py
+++ b/exercises/practice/rational-numbers/rational_numbers_test.py
@@ -131,6 +131,9 @@ class RationalNumbersTest(unittest.TestCase):
     def test_reduce_a_positive_rational_number_to_lowest_terms(self):
         self.assertEqual(Rational(2, 4), Rational(1, 2))
 
+    def test_reduce_places_the_minus_sign_on_the_numerator(self):
+        self.assertEqual(Rational(3, -4), Rational(-3, 4))
+
     def test_reduce_a_negative_rational_number_to_lowest_terms(self):
         self.assertEqual(Rational(-4, 6), Rational(-2, 3))
 
diff --git a/exercises/practice/resistor-color-duo/.meta/tests.toml b/exercises/practice/resistor-color-duo/.meta/tests.toml
index 862b5770..9036fc78 100644
--- a/exercises/practice/resistor-color-duo/.meta/tests.toml
+++ b/exercises/practice/resistor-color-duo/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [ce11995a-5b93-4950-a5e9-93423693b2fc]
 description = "Brown and black"
@@ -11,8 +18,14 @@ description = "Blue and grey"
 [f1886361-fdfd-4693-acf8-46726fe24e0c]
 description = "Yellow and violet"
 
+[b7a6cbd2-ae3c-470a-93eb-56670b305640]
+description = "White and red"
+
 [77a8293d-2a83-4016-b1af-991acc12b9fe]
 description = "Orange and orange"
 
 [0c4fb44f-db7c-4d03-afa8-054350f156a8]
 description = "Ignore additional colors"
+
+[4a8ceec5-0ab4-4904-88a4-daf953a5e818]
+description = "Black and brown, one-digit"
diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
index c259f5db..72df473f 100644
--- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
+++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
@@ -17,12 +17,14 @@ class ResistorColorDuoTest(unittest.TestCase):
     def test_yellow_and_violet(self):
         self.assertEqual(value(["yellow", "violet"]), 47)
 
+    def test_white_and_red(self):
+        self.assertEqual(value(["white", "red"]), 92)
+
     def test_orange_and_orange(self):
         self.assertEqual(value(["orange", "orange"]), 33)
 
     def test_ignore_additional_colors(self):
         self.assertEqual(value(["green", "brown", "orange"]), 51)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_black_and_brown_one_digit(self):
+        self.assertEqual(value(["black", "brown"]), 1)
diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py
index 1bc4b60c..0d14b6e7 100644
--- a/exercises/practice/say/say_test.py
+++ b/exercises/practice/say/say_test.py
@@ -53,21 +53,19 @@ class SayTest(unittest.TestCase):
         )
 
     def test_numbers_below_zero_are_out_of_range(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             say(-1)
 
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "input out of range")
+
     def test_numbers_above_999_999_999_999_are_out_of_range(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             say(1000000000000)
 
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "input out of range")
+
     # Additional tests for this track
     def test_one_hundred_seventy(self):
         self.assertEqual(say(170), "one hundred seventy")
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py
index 78fb6619..c4a63712 100644
--- a/exercises/practice/series/series_test.py
+++ b/exercises/practice/series/series_test.py
@@ -30,25 +30,27 @@ class SeriesTest(unittest.TestCase):
         )
 
     def test_slice_length_is_too_large(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             slices("12345", 6)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(
+            err.exception.args[0], "slice length cannot be greater than series length"
+        )
 
     def test_slice_length_cannot_be_zero(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             slices("12345", 0)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "slice length cannot be zero")
 
     def test_slice_length_cannot_be_negative(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             slices("123", -1)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "slice length cannot be negative")
 
     def test_empty_series_is_invalid(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             slices("", 1)
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "series cannot be empty")
diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py
index 5f8ad91e..ff4fac2f 100644
--- a/exercises/practice/sgf-parsing/sgf_parsing_test.py
+++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py
@@ -11,18 +11,24 @@ from sgf_parsing import (
 class SgfParsingTest(unittest.TestCase):
     def test_empty_input(self):
         input_string = ""
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "tree missing")
 
     def test_tree_with_no_nodes(self):
         input_string = "()"
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "tree with no nodes")
 
     def test_node_without_tree(self):
         input_string = ";"
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "tree missing")
 
     def test_node_without_properties(self):
         input_string = "(;)"
@@ -41,18 +47,24 @@ class SgfParsingTest(unittest.TestCase):
 
     def test_properties_without_delimiter(self):
         input_string = "(;A)"
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "properties without delimiter")
 
     def test_all_lowercase_property(self):
         input_string = "(;a[b])"
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "property must be in uppercase")
 
     def test_upper_and_lowercase_property(self):
         input_string = "(;Aa[b])"
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             parse(input_string)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "property must be in uppercase")
 
     def test_two_nodes(self):
         input_string = "(;A[B];B[C])"
@@ -76,11 +88,3 @@ class SgfParsingTest(unittest.TestCase):
         input_string = "(;A[\\]b\nc\nd\t\te \n\\]])"
         expected = SgfTree(properties={"A": ["]b\nc\nd  e \n]"]})
         self.assertEqual(parse(input_string), expected)
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/simple-linked-list/simple_linked_list_test.py b/exercises/practice/simple-linked-list/simple_linked_list_test.py
index f234dc8f..91025e0e 100644
--- a/exercises/practice/simple-linked-list/simple_linked_list_test.py
+++ b/exercises/practice/simple-linked-list/simple_linked_list_test.py
@@ -20,8 +20,10 @@ class SimpleLinkedListTest(unittest.TestCase):
 
     def test_error_on_empty_list_head(self):
         sut = LinkedList()
-        with self.assertRaisesWithMessage(EmptyListException):
+        with self.assertRaises(EmptyListException) as err:
             sut.head()
+        self.assertEqual(type(err.exception), EmptyListException)
+        self.assertEqual(err.exception.args[0], "The list is empty.")
 
     def test_singleton_list_has_head(self):
         sut = LinkedList([1])
@@ -51,13 +53,17 @@ class SimpleLinkedListTest(unittest.TestCase):
     def test_pop_from_singleton_list_removes_head(self):
         sut = LinkedList([1])
         self.assertEqual(sut.pop(), 1)
-        with self.assertRaisesWithMessage(EmptyListException):
+        with self.assertRaises(EmptyListException) as err:
             sut.head()
+        self.assertEqual(type(err.exception), EmptyListException)
+        self.assertEqual(err.exception.args[0], "The list is empty.")
 
     def test_error_on_empty_list_pop(self):
         sut = LinkedList()
-        with self.assertRaisesWithMessage(EmptyListException):
+        with self.assertRaises(EmptyListException) as err:
             sut.pop()
+        self.assertEqual(type(err.exception), EmptyListException)
+        self.assertEqual(err.exception.args[0], "The list is empty.")
 
     def test_push_and_pop(self):
         sut = LinkedList([1, 2])
@@ -106,11 +112,3 @@ class SimpleLinkedListTest(unittest.TestCase):
     def test_reverse_non_empty_list(self):
         sut = LinkedList([1, 2, 3])
         self.assertEqual(list(sut.reversed()), [1, 2, 3])
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml
index 74bd60cc..de5020a9 100644
--- a/exercises/practice/sublist/.meta/tests.toml
+++ b/exercises/practice/sublist/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [97319c93-ebc5-47ab-a022-02a1980e1d29]
 description = "empty lists"
@@ -47,6 +54,9 @@ description = "first list missing element from second list"
 [83ffe6d8-a445-4a3c-8795-1e51a95e65c3]
 description = "second list missing element from first list"
 
+[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89]
+description = "first list missing additional digits from second list"
+
 [0d7ee7c1-0347-45c8-9ef5-b88db152b30b]
 description = "order matters to a list"
 
diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py
index 03ff9ceb..b3dc9c7b 100644
--- a/exercises/practice/sublist/sublist_test.py
+++ b/exercises/practice/sublist/sublist_test.py
@@ -57,6 +57,9 @@ class SublistTest(unittest.TestCase):
     def test_second_list_missing_element_from_first_list(self):
         self.assertEqual(sublist([1, 2, 3], [1, 3]), UNEQUAL)
 
+    def test_first_list_missing_additional_digits_from_second_list(self):
+        self.assertEqual(sublist([1, 2], [1, 22]), UNEQUAL)
+
     def test_order_matters_to_a_list(self):
         self.assertEqual(sublist([1, 2, 3], [3, 2, 1]), UNEQUAL)
 
@@ -83,7 +86,3 @@ class SublistTest(unittest.TestCase):
         self.assertEqual(
             sublist(list(range(3, 200, 3)), list(range(15, 200, 15))), UNEQUAL
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/tournament/.meta/tests.toml b/exercises/practice/tournament/.meta/tests.toml
index 59867708..0e33c87f 100644
--- a/exercises/practice/tournament/.meta/tests.toml
+++ b/exercises/practice/tournament/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [67e9fab1-07c1-49cf-9159-bc8671cc7c9c]
 description = "just the header if no input"
@@ -34,3 +41,6 @@ description = "incomplete competition (not all pairs have played)"
 
 [3aa0386f-150b-4f99-90bb-5195e7b7d3b8]
 description = "ties broken alphabetically"
+
+[f9e20931-8a65-442a-81f6-503c0205b17a]
+description = "ensure points sorted numerically"
diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py
index dac4c15c..22a81667 100644
--- a/exercises/practice/tournament/tournament_test.py
+++ b/exercises/practice/tournament/tournament_test.py
@@ -139,6 +139,17 @@ class TournamentTest(unittest.TestCase):
         ]
         self.assertEqual(tally(results), table)
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_ensure_points_sorted_numerically(self):
+        results = [
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Devastating Donkeys;Blithering Badgers;win",
+            "Blithering Badgers;Devastating Donkeys;win",
+        ]
+        table = [
+            "Team                           | MP |  W |  D |  L |  P",
+            "Devastating Donkeys            |  5 |  4 |  0 |  1 | 12",
+            "Blithering Badgers             |  5 |  1 |  0 |  4 |  3",
+        ]
+        self.assertEqual(tally(results), table)
diff --git a/exercises/practice/transpose/.meta/tests.toml b/exercises/practice/transpose/.meta/tests.toml
index 7faa8aea..32e366fb 100644
--- a/exercises/practice/transpose/.meta/tests.toml
+++ b/exercises/practice/transpose/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
 
 [404b7262-c050-4df0-a2a2-0cb06cd6a821]
 description = "empty string"
@@ -34,3 +41,6 @@ description = "rectangle"
 
 [b80badc9-057e-4543-bd07-ce1296a1ea2c]
 description = "triangle"
+
+[76acfd50-5596-4d05-89f1-5116328a7dd9]
+description = "jagged triangle"
diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py
index c47a3fdb..0a0f5c02 100644
--- a/exercises/practice/transpose/transpose_test.py
+++ b/exercises/practice/transpose/transpose_test.py
@@ -115,6 +115,7 @@ class TransposeTest(unittest.TestCase):
         expected = ["TEASER", " EASER", "  ASER", "   SER", "    ER", "     R"]
         self.assertEqual(transpose("\n".join(lines)), "\n".join(expected))
 
-
-if __name__ == "__main__":
-    unittest.main()
+    def test_jagged_triangle(self):
+        lines = ["11", "2", "3333", "444", "555555", "66666"]
+        expected = ["123456", "1 3456", "  3456", "  3 56", "    56", "    5"]
+        self.assertEqual(transpose("\n".join(lines)), "\n".join(expected))
diff --git a/exercises/practice/tree-building/tree_building_test.py b/exercises/practice/tree-building/tree_building_test.py
index 20f20774..bda851db 100644
--- a/exercises/practice/tree-building/tree_building_test.py
+++ b/exercises/practice/tree-building/tree_building_test.py
@@ -108,8 +108,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(1, 0)
         ]
         # Root parent_id should be equal to record_id(0)
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Node record_id should be smaller than it's parent_id.")
 
     def test_no_root_node(self):
         records = [
@@ -117,8 +119,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(2, 0)
         ]
         # Record with record_id 0 (root) is missing
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Record id is invalid or out of order.")
 
     def test_non_continuous(self):
         records = [
@@ -128,8 +132,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(0, 0)
         ]
         # Record with record_id 3 is missing
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Record id is invalid or out of order.")
 
     def test_cycle_directly(self):
         records = [
@@ -142,8 +148,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(6, 3)
         ]
         # Cycle caused by Record 2 with parent_id pointing to itself
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Only root should have equal record and parent id.")
 
     def test_cycle_indirectly(self):
         records = [
@@ -156,8 +164,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(6, 3)
         ]
         # Cycle caused by Record 2 with parent_id(6) greater than record_id(2)
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Node record_id should be smaller than it's parent_id.")
 
     def test_higher_id_parent_of_lower_id(self):
         records = [
@@ -166,8 +176,10 @@ class TreeBuildingTest(unittest.TestCase):
             Record(1, 2)
         ]
         # Record 1 have parent_id(2) greater than record_id(1)
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             BuildTree(records)
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "Node record_id should be smaller than it's parent_id.")
 
     def assert_node_is_branch(self, node, node_id, children_count):
         self.assertEqual(node.node_id, node_id)
@@ -177,11 +189,3 @@ class TreeBuildingTest(unittest.TestCase):
     def assert_node_is_leaf(self, node, node_id):
         self.assertEqual(node.node_id, node_id)
         self.assertEqual(len(node.children), 0)
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py
index e6550d64..c6867dac 100644
--- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py
+++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py
@@ -100,12 +100,16 @@ class VariableLengthQuantityTest(unittest.TestCase):
         self.assertEqual(decode([0x8F, 0xFF, 0xFF, 0xFF, 0x7F]), [0xFFFFFFFF])
 
     def test_incomplete_sequence_causes_error(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             decode([0xFF])
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "incomplete sequence")
 
     def test_incomplete_sequence_causes_error_even_if_value_is_zero(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             decode([0x80])
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "incomplete sequence")
 
     def test_multiple_values(self):
         self.assertEqual(
@@ -130,11 +134,3 @@ class VariableLengthQuantityTest(unittest.TestCase):
             ),
             [0x2000, 0x123456, 0xFFFFFFF, 0x0, 0x3FFF, 0x4000],
         )
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py
index 101aef23..afa75017 100644
--- a/exercises/practice/wordy/wordy_test.py
+++ b/exercises/practice/wordy/wordy_test.py
@@ -54,51 +54,63 @@ class WordyTest(unittest.TestCase):
         self.assertEqual(answer("What is -12 divided by 2 divided by -3?"), 2)
 
     def test_unknown_operation(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 52 cubed?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "unknown operation")
 
     def test_non_math_question(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("Who is the President of the United States?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "unknown operation")
 
     def test_reject_problem_missing_an_operand(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 1 plus?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     def test_reject_problem_with_no_operands_or_operators(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     def test_reject_two_operations_in_a_row(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 1 plus plus 2?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     def test_reject_two_numbers_in_a_row(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 1 plus 2 1?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     def test_reject_postfix_notation(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 1 2 plus?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     def test_reject_prefix_notation(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is plus 1 2?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "syntax error")
 
     # Additional tests for this track
 
     def test_missing_operation(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 2 2 minus 3?")
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "unknown operation")
 
     def test_missing_number(self):
-        with self.assertRaisesWithMessage(ValueError):
+        with self.assertRaises(ValueError) as err:
             answer("What is 7 plus multiplied by -2?")
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
+        self.assertEqual(type(err.exception), ValueError)
+        self.assertEqual(err.exception.args[0], "unknown operation")