Repository: exercism/clojure
Pull request #632 (2024-04-25 06:51:32)

🤖 Sync org-wide files to upstream repo
ℹ More info: https://github.com/exercism/org-wide-files/commit/5ccdeb6ebe24182f48466a9e7cc27370cdcbcfe7
👁 Tracking issue: https://github.com/exercism/org-wide-files/issues/360


Patch:
diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml
index b940c59..812e912 100644
--- a/.github/workflows/no-important-files-changed.yml
+++ b/.github/workflows/no-important-files-changed.yml
@@ -16,7 +16,7 @@ permissions:
   pull-requests: write
 
 jobs:
-  pause:
+  check:
     uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main
     with:
       repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }}
diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml
new file mode 100644
index 0000000..b6ec9c5
--- /dev/null
+++ b/.github/workflows/ping-cross-track-maintainers-team.yml
@@ -0,0 +1,16 @@
+name: Ping cross-track maintainers team
+
+on:
+  pull_request_target:
+    types:
+      - opened
+
+permissions:
+  pull-requests: write
+
+jobs:
+  ping:
+    if: github.repository_owner == 'exercism' # Stops this job from running on forks
+    uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main
+    secrets:
+      github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e9ff73f..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-language: clojure
-sudo: false
-lein: lein
-jdk: openjdk8
-script:
-- bin/fetch-configlet
-- bin/configlet lint
-- lein test
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 6cb850b..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-version: 1.0.{build}
-install:
-- cmd: >-
-    choco install lein
-build: off
-test_script:
-- cmd: >-
-    cd %APPVEYOR_BUILD_FOLDER%
-
-    lein test
-    
\ No newline at end of file
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4800e15..6bef43a 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -24,10 +24,11 @@ get_download_url() {
   local latest='https://api.github.com/repos/exercism/configlet/releases/latest'
   local arch
   case "$(uname -m)" in
-    x86_64) arch='x86-64' ;;
-    *686*)  arch='i386'   ;;
-    *386*)  arch='i386'   ;;
-    *)      arch='x86-64' ;;
+    aarch64|arm64) arch='arm64'  ;;
+    x86_64)        arch='x86-64' ;;
+    *686*)         arch='i386'   ;;
+    *386*)         arch='i386'   ;;
+    *)             arch='x86-64' ;;
   esac
   local suffix="${os}_${arch}.${ext}"
   curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" |
@@ -47,7 +48,7 @@ main() {
   fi
 
   local os
-  case "$(uname)" in
+  case "$(uname -s)" in
     Darwin*)   os='macos'   ;;
     Linux*)    os='linux'   ;;
     Windows*)  os='windows' ;;
@@ -58,8 +59,8 @@ main() {
 
   local ext
   case "${os}" in
-    windows*) ext='zip'    ;;
-    *)        ext='tar.gz' ;;
+    windows) ext='zip'    ;;
+    *)       ext='tar.gz' ;;
   esac
 
   echo "Fetching configlet..." >&2
@@ -69,16 +70,16 @@ main() {
   curl "${curlopts[@]}" --output "${output_path}" "${download_url}"
 
   case "${ext}" in
-    *zip) unzip "${output_path}" -d "${output_dir}"   ;;
-    *)    tar xzf "${output_path}" -C "${output_dir}" ;;
+    zip) unzip "${output_path}" -d "${output_dir}"   ;;
+    *)   tar xzf "${output_path}" -C "${output_dir}" ;;
   esac
 
   rm -f "${output_path}"
 
   local executable_ext
   case "${os}" in
-    windows*) executable_ext='.exe' ;;
-    *)        executable_ext=''     ;;
+    windows) executable_ext='.exe' ;;
+    *)       executable_ext=''     ;;
   esac
 
   local configlet_path="${output_dir}/configlet${executable_ext}"
diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md
index b8bbd5a..daf8cfd 100644
--- a/exercises/practice/allergies/.docs/instructions.md
+++ b/exercises/practice/allergies/.docs/instructions.md
@@ -2,20 +2,18 @@
 
 Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.
 
-An allergy test produces a single numeric score which contains the
-information about all the allergies the person has (that they were
-tested for).
+An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for).
 
 The list of items (and their value) that were tested are:
 
-* eggs (1)
-* peanuts (2)
-* shellfish (4)
-* strawberries (8)
-* tomatoes (16)
-* chocolate (32)
-* pollen (64)
-* cats (128)
+- eggs (1)
+- peanuts (2)
+- shellfish (4)
+- strawberries (8)
+- tomatoes (16)
+- chocolate (32)
+- pollen (64)
+- cats (128)
 
 So if Tom is allergic to peanuts and chocolate, he gets a score of 34.
 
@@ -24,7 +22,6 @@ Now, given just that score of 34, your program should be able to say:
 - Whether Tom is allergic to any one of those allergens listed above.
 - All the allergens Tom is allergic to.
 
-Note: a given score may include allergens **not** listed above (i.e.
-allergens that score 256, 512, 1024, etc.).  Your program should
-ignore those components of the score.  For example, if the allergy
-score is 257, your program should only report the eggs (1) allergy.
+Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.).
+Your program should ignore those components of the score.
+For example, if the allergy score is 257, your program should only report the eggs (1) allergy.
diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json
index 8323d0c..a137e05 100644
--- a/exercises/practice/allergies/.meta/config.json
+++ b/exercises/practice/allergies/.meta/config.json
@@ -25,6 +25,6 @@
     ]
   },
   "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.",
-  "source": "Jumpstart Lab Warm-up",
-  "source_url": "http://jumpstartlab.com"
+  "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.",
+  "source_url": "https://turing.edu"
 }
diff --git a/exercises/practice/allergies/.meta/src/example.clj b/exercises/practice/allergies/.meta/src/example.clj
index cba06a7..03c7b82 100644
--- a/exercises/practice/allergies/.meta/src/example.clj
+++ b/exercises/practice/allergies/.meta/src/example.clj
@@ -3,22 +3,13 @@
 (def ^:private allergens
   [:eggs :peanuts :shellfish :strawberries :tomatoes :chocolate :pollen :cats])
 
-(defn- flagged?
-  [flags index]
-  (-> (bit-shift-right flags index)
-      (bit-and 1)
-      (pos?)))
-
-(defn allergies
-  "Given an 8-bit bitmap of flags, return the list of matching allergens."
-  [flags]
-  (keep-indexed (fn [index allergen]
-                  (when (flagged? flags index)
-                    allergen))
-                allergens))
+(def ^:private allergen->power
+  (zipmap allergens (range (count allergens))))
 
 (defn allergic-to?
-  "Given an 8-bit bitmap of flags and an allergen, return a boolean
-  indicating whether or not the patient is allergic to the given allergen."
-  [flags allergen]
-  (some #{allergen} (allergies flags)))
+  [score allergen]
+  (bit-test score (allergen->power allergen)))
+
+(defn allergies
+  [score]
+  (keep-indexed #(if (bit-test score %1) %2) allergens))
diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md
index 2675b58..a729848 100644
--- a/exercises/practice/anagram/.docs/instructions.md
+++ b/exercises/practice/anagram/.docs/instructions.md
@@ -1,8 +1,13 @@
 # Instructions
 
-An anagram is a rearrangement of letters to form a new word.
-Given a word and a list of candidates, select the sublist of anagrams of the given word.
+Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target.
 
-Given `"listen"` and a list of candidates like `"enlists" "google"
-"inlets" "banana"` the program should return a list containing
-`"inlets"`.
+An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`.
+A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`.
+
+The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`).
+Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`.
+The anagram set is the subset of the candidate set that are anagrams of the target (in any order).
+Words in the anagram set should have the same letter case as in the candidate set.
+
+Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`.
diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md
new file mode 100644
index 0000000..1acbdf0
--- /dev/null
+++ b/exercises/practice/anagram/.docs/introduction.md
@@ -0,0 +1,12 @@
+# Introduction
+
+At a garage sale, you find a lovely vintage typewriter at a bargain price!
+Excitedly, you rush home, insert a sheet of paper, and start typing away.
+However, your excitement wanes when you examine the output: all words are garbled!
+For example, it prints "stop" instead of "post" and "least" instead of "stale."
+Carefully, you try again, but now it prints "spot" and "slate."
+After some experimentation, you find there is a random delay before each letter is printed, which messes up the order.
+You now understand why they sold it for so little money!
+
+You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word.
+Pleased with your finding, you spend the rest of the day generating hundreds of anagrams.
diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md
index 1265ac8..0955520 100644
--- a/exercises/practice/bank-account/.docs/instructions.md
+++ b/exercises/practice/bank-account/.docs/instructions.md
@@ -1,27 +1,10 @@
 # Instructions
 
-Simulate a bank account supporting opening/closing, withdrawals, and deposits
-of money. Watch out for concurrent transactions!
+Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money.
 
-A bank account can be accessed in multiple ways. Clients can make
-deposits and withdrawals using the internet, mobile phones, etc. Shops
-can charge against the account.
+As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel.
+For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there is no [race conditions][wikipedia] between when you read the account balance and set the new balance.
 
-Create an account that can be accessed from multiple threads/processes
-(terminology depends on your programming language).
+It should be possible to close an account; operations against a closed account must fail.
 
-It should be possible to close an account; operations against a closed
-account must fail.
-
-## Instructions
-
-Run the test file, and fix each of the errors in turn. When you get the
-first test to pass, go to the first pending or skipped test, and make
-that pass as well. When all of the tests are passing, feel free to
-submit.
-
-Remember that passing code is just the first step. The goal is to work
-towards a solution that is as readable and expressive as you can make
-it.
-
-Have fun!
+[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software
diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md
new file mode 100644
index 0000000..650b5d9
--- /dev/null
+++ b/exercises/practice/bank-account/.docs/introduction.md
@@ -0,0 +1,20 @@
+# Introduction
+
+After years of filling out forms and waiting, you've finally acquired your banking license.
+This means you are now officially eligible to open your own bank, hurray!
+
+Your first priority is to get the IT systems up and running.
+After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits.
+
+Since you couldn't be bothered writing tests, you invite some friends to help test the system.
+However, after just five minutes, one of your friends claims they've lost money!
+While you're confident your code is bug-free, you start looking through the logs to investigate.
+
+Ah yes, just as you suspected, your friend is at fault!
+They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_.
+Who would do such a thing?
+
+While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this.
+Thus, no parallel banking support, no go-live signal.
+Sighing, you create a mental note to work on this tomorrow.
+This will set your launch date back at _least_ one more day, but well...
diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md
index aa1946c..12f4358 100644
--- a/exercises/practice/binary-search/.docs/instructions.md
+++ b/exercises/practice/binary-search/.docs/instructions.md
@@ -11,7 +11,7 @@ Binary search only works when a list has been sorted.
 
 The algorithm looks like this:
 
-- Find the middle element of a *sorted* list and compare it with the item we're looking for.
+- Find the middle element of a _sorted_ list and compare it with the item we're looking for.
 - If the middle element is our item, then we're done!
 - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it.
 - If the middle element is less than our item, we can eliminate that element and all the elements **before** it.
diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json
index 74b15a0..e2fe40d 100644
--- a/exercises/practice/binary-search/.meta/config.json
+++ b/exercises/practice/binary-search/.meta/config.json
@@ -24,5 +24,5 @@
   },
   "blurb": "Implement a binary search algorithm.",
   "source": "Wikipedia",
-  "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm"
+  "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm"
 }
diff --git a/exercises/practice/binary-search/.meta/src/example.clj b/exercises/practice/binary-search/.meta/src/example.clj
index 9ea77cf..416b0da 100644
--- a/exercises/practice/binary-search/.meta/src/example.clj
+++ b/exercises/practice/binary-search/.meta/src/example.clj
@@ -1,14 +1,15 @@
 (ns binary-search)
 
-(defn middle [alist]
-  (-> alist (count) (quot 2)))
-
 (defn search-for
-  [elem alist]
-  (let [middle (middle alist)
-        cur-elem (nth alist middle)]
-    (cond
-      (= cur-elem elem) middle
-      (or (= middle (count alist)) (zero? middle)) (throw (Exception. (format "%s not found in list" elem)))
-      (< cur-elem elem) (+ middle (search-for elem (drop middle alist)))
-      (> cur-elem elem) (search-for elem (take middle alist)))))
+  [n coll]
+  (let [coll (vec coll)]
+    (loop [low-idx 0
+           high-idx (dec (count coll))]
+      (if (> low-idx high-idx)
+        (throw (Exception. "not found"))
+        (let [mid-index (quot (+ high-idx low-idx) 2)
+              mid-item (get coll mid-index)]
+          (cond
+            (= n mid-item) mid-index
+            (> mid-item n) (recur low-idx (dec mid-index))
+            :else (recur (inc mid-index) high-idx)))))))
diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md
index ba89ff9..6fe11a5 100644
--- a/exercises/practice/kindergarten-garden/.docs/instructions.md
+++ b/exercises/practice/kindergarten-garden/.docs/instructions.md
@@ -1,17 +1,21 @@
 # Instructions
 
-Given a diagram, determine which plants each child in the kindergarten class is
-responsible for.
+Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for.
 
-The kindergarten class is learning about growing plants. The teacher
-thought it would be a good idea to give them actual seeds, plant them in
-actual dirt, and grow actual plants.
+There are 12 children in the class:
+
+- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry.
+
+Four different types of seeds are planted:
 
-They've chosen to grow grass, clover, radishes, and violets.
+| Plant  | Diagram encoding |
+| ------ | ---------------- |
+| Grass  | G                |
+| Clover | C                |
+| Radish | R                |
+| Violet | V                |
 
-To this end, the children have put little cups along the window sills, and
-planted one type of plant in each cup, choosing randomly from the available
-types of seeds.
+Each child gets four cups, two on each row:
 
 ```text
 [window][window][window]
@@ -19,16 +23,9 @@ types of seeds.
 ........................
 ```
 
-There are 12 children in the class:
-
-- Alice, Bob, Charlie, David,
-- Eve, Fred, Ginny, Harriet,
-- Ileana, Joseph, Kincaid, and Larry.
-
-Each child gets 4 cups, two on each row. Their teacher assigns cups to
-the children alphabetically by their names.
+Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last.
 
-The following diagram represents Alice's plants:
+Here is an example diagram representing Alice's plants:
 
 ```text
 [window][window][window]
@@ -36,12 +33,11 @@ VR......................
 RG......................
 ```
 
-In the first row, nearest the windows, she has a violet and a radish.  In the
-second row she has a radish and some grass.
+In the first row, nearest the windows, she has a violet and a radish.
+In the second row she has a radish and some grass.
 
-Your program will be given the plants from left-to-right starting with
-the row nearest the windows. From this, it should be able to determine
-which plants belong to each student.
+Your program will be given the plants from left-to-right starting with the row nearest the windows.
+From this, it should be able to determine which plants belong to each student.
 
 For example, if it's told that the garden looks like so:
 
diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md
new file mode 100644
index 0000000..5ad97d2
--- /dev/null
+++ b/exercises/practice/kindergarten-garden/.docs/introduction.md
@@ -0,0 +1,6 @@
+# Introduction
+
+The kindergarten class is learning about growing plants.
+The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt.
+To this end, the children have put little cups along the window sills and planted one type of plant in each cup.
+The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets.
diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json
index a5668ab..cea6fa5 100644
--- a/exercises/practice/kindergarten-garden/.meta/config.json
+++ b/exercises/practice/kindergarten-garden/.meta/config.json
@@ -28,6 +28,6 @@
     ]
   },
   "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.",
-  "source": "Random musings during airplane trip.",
-  "source_url": "http://jumpstartlab.com"
+  "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.",
+  "source_url": "https://turing.edu"
 }
diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md
index c7c7d3e..8cbe791 100644
--- a/exercises/practice/luhn/.docs/instructions.md
+++ b/exercises/practice/luhn/.docs/instructions.md
@@ -2,35 +2,31 @@
 
 Given a number determine whether or not it is valid per the Luhn formula.
 
-The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is
-a simple checksum formula used to validate a variety of identification
-numbers, such as credit card numbers and Canadian Social Insurance
-Numbers.
+The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance 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.
+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
 ```
 
-The first step of the Luhn algorithm is to double every second digit,
-starting from the right. We will be doubling
+The first step of the Luhn algorithm is to double every second digit, starting from the right.
+We will be doubling
 
 ```text
 4_3_ 3_9_ 0_4_ 6_6_
 ```
 
-If doubling the number results in a number greater than 9 then subtract 9
-from the product. The results of our doubling:
+If doubling the number results in a number greater than 9 then subtract 9 from the product.
+The results of our doubling:
 
 ```text
 8569 6195 0383 3437
@@ -42,9 +38,10 @@ Then sum all of the digits:
 8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80
 ```
 
-If the sum is evenly divisible by 10, then the number is valid. This number is valid!
+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
@@ -63,3 +60,5 @@ Sum the digits
 ```
 
 57 is not evenly divisible by 10, so this number is not valid.
+
+[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm
diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json
index fcc88e6..c03a923 100644
--- a/exercises/practice/luhn/.meta/config.json
+++ b/exercises/practice/luhn/.meta/config.json
@@ -21,5 +21,5 @@
   },
   "blurb": "Given a number determine whether or not it is valid per the Luhn formula.",
   "source": "The Luhn Algorithm on Wikipedia",
-  "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm"
+  "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm"
 }
diff --git a/exercises/practice/luhn/.meta/src/example.clj b/exercises/practice/luhn/.meta/src/example.clj
index c6b4521..4290395 100644
--- a/exercises/practice/luhn/.meta/src/example.clj
+++ b/exercises/practice/luhn/.meta/src/example.clj
@@ -1,34 +1,47 @@
-(ns luhn
-  (:require [clojure.string :as string]))
-
-(defn to-reversed-digits
-  "returns a lazy sequence of least to most significant digits of n"
-  [n]
-  (->> [n 0]
-       (iterate (fn [[i _]] [(quot i 10) (mod i 10)]))
-       (take-while (complement #{[0 0]}))
-       (map second)
-       rest))
-
-(defn checksum
-  "returns the luhn checksum of n, assuming it has a check digit"
-  [n]
-  (-> (->> n
-           to-reversed-digits
-           (map * (cycle [1 2]))
-           (map #(if (>= % 10) (- % 9) %))
-           (apply +))
-      (mod 10)))
-
-(defn string->long
-  "Strips any non-digit characters and converts the string into a Long"
-  [n]
-  (-> n (string/replace #"[^0-9]+" "") Long/parseLong))
+(ns luhn)
+
+(defn string->digits
+  [s]
+  (map #(Character/digit ^char % 10) s))
+
+(defn valid-integer?
+  [digits]
+  (and (> (count digits) 1)
+       (every? #(<= 0 % 9) digits)))
+
+(defn zero-pad
+  [digits]
+  (if (even? (count digits))
+    digits
+    (conj digits 0)))
+
+(defn transform-digit
+  [index val]
+  (if (odd? index)
+    val
+    (let [dval (* 2 val)]
+      (if (> dval 9)
+        (- dval 9)
+        dval))))
+
+(defn get-luhn-value
+  [digits]
+  (let [padded-digits (zero-pad digits)]
+    (->> padded-digits
+         (map-indexed transform-digit)
+         (reduce +))))
+
+(defn valid-luhn-value?
+  [val]
+  (int? (/ val 10)))
+
+(defn remove-spaces
+  [s]
+  (remove #{\space} s))
 
 (defn valid?
-  "whether n has a valid luhn check-digit"
-  [n]
-  ; Numbers with non digit/whitespace or only 1 digit are invalid
-  (if (or (re-find #"[^0-9\s]+" n) (>= 1 (count (string/trim n))))
-    false
-    (zero? (-> n string->long checksum))))
+  "Returns true if the given string represents a valid luhn number, else false"
+  [s]
+  (let [digits (string->digits (remove-spaces s))]
+    (and (valid-integer? digits)
+         (valid-luhn-value? (get-luhn-value digits)))))
diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md
index fe1a9c2..000de2f 100644
--- a/exercises/practice/meetup/.docs/instructions.md
+++ b/exercises/practice/meetup/.docs/instructions.md
@@ -1,27 +1,34 @@
 # Instructions
 
-Calculate the date of meetups.
+Your task is to find the exact date of a meetup, given a month, year, weekday and week.
 
-Typically meetups happen on the same day of the week.  In this exercise, you
-will take a description of a meetup date, and return the actual meetup date.
+There are five week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`.
 
-Examples of general descriptions are:
+For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018).
 
-- The first Monday of January 2017
-- The third Tuesday of January 2017
-- The wednesteenth of January 2017
-- The last Thursday of January 2017
+Similarly, you might be asked to find:
 
-The descriptors you are expected to parse are:
-first, second, third, fourth, fifth, last, monteenth, tuesteenth, wednesteenth,
-thursteenth, friteenth, saturteenth, sunteenth
+- the third Tuesday of August 2019 (August 20, 2019)
+- the teenth Wednesday of May 2020 (May 13, 2020)
+- the fourth Sunday of July 2021 (July 25, 2021)
+- the last Thursday of November 2022 (November 24, 2022)
+- the teenth Saturday of August 1953 (August 15, 1953)
 
-Note that "monteenth", "tuesteenth", etc are all made up words. There was a
-meetup whose members realized that there are exactly 7 numbered days in a month
-that end in '-teenth'. Therefore, one is guaranteed that each day of the week
-(Monday, Tuesday, ...) will have exactly one date that is named with '-teenth'
-in every month.
+## Teenth
 
-Given examples of a meetup dates, each containing a month, day, year, and
-descriptor calculate the date of the actual meetup.  For example, if given
-"The first Monday of January 2017", the correct meetup date is 2017/1/2.
+The teenth week refers to the seven days in a month that end in '-teenth' (13th, 14th, 15th, 16th, 17th, 18th and 19th).
+
+If asked to find the teenth Saturday of August, 1953, we check its calendar:
+
+```plaintext
+    August 1953
+Su Mo Tu We Th Fr Sa
+                   1
+ 2  3  4  5  6  7  8
+ 9 10 11 12 13 14 15
+16 17 18 19 20 21 22
+23 24 25 26 27 28 29
+30 31
+```
+
+From this we find that the teenth Saturday is August 15, 1953.
diff --git a/exercises/practice/meetup/.docs/introduction.md b/exercises/practice/meetup/.docs/introduction.md
new file mode 100644
index 0000000..29170ef
--- /dev/null
+++ b/exercises/practice/meetup/.docs/introduction.md
@@ -0,0 +1,29 @@
+# Introduction
+
+Every month, your partner meets up with their best friend.
+Both of them have very busy schedules, making it challenging to find a suitable date!
+Given your own busy schedule, your partner always double-checks potential meetup dates with you:
+
+- "Can I meet up on the first Friday of next month?"
+- "What about the third Wednesday?"
+- "Maybe the last Sunday?"
+
+In this month's call, your partner asked you this question:
+
+- "I'd like to meet up on the teenth Thursday; is that okay?"
+
+Confused, you ask what a "teenth" day is.
+Your partner explains that a teenth day, a concept they made up, refers to the days in a month that end in '-teenth':
+
+- 13th (thirteenth)
+- 14th (fourteenth)
+- 15th (fifteenth)
+- 16th (sixteenth)
+- 17th (seventeenth)
+- 18th (eighteenth)
+- 19th (nineteenth)
+
+As there are also seven weekdays, it is guaranteed that each day of the week has _exactly one_ teenth day each month.
+
+Now that you understand the concept of a teenth day, you check your calendar.
+You don't have anything planned on the teenth Thursday, so you happily confirm the date with your partner.
diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json
index 240a6a3..c21c0b7 100644
--- a/exercises/practice/meetup/.meta/config.json
+++ b/exercises/practice/meetup/.meta/config.json
@@ -26,6 +26,5 @@
     ]
   },
   "blurb": "Calculate the date of meetups.",
-  "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month",
-  "source_url": "https://twitter.com/copiousfreetime"
+  "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month"
 }
diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md
index bcb1251..a9645ac 100644
--- a/exercises/practice/pig-latin/.docs/instructions.md
+++ b/exercises/practice/pig-latin/.docs/instructions.md
@@ -1,18 +1,46 @@
 # Instructions
 
-Implement a program that translates from English to Pig Latin.
+Your task is to translate text from English to Pig Latin.
+The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word.
+These rules look at each word's use of vowels and consonants:
 
-Pig Latin is a made-up children's language that's intended to be
-confusing. It obeys a few simple rules (below), but when it's spoken
-quickly it's really difficult for non-children (and non-native speakers)
-to understand.
+- vowels: the letters `a`, `e`, `i`, `o`, and `u`
+- consonants: the other 21 letters of the English alphabet
 
-- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay").
-- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay").
-- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay").
-- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay").
+## Rule 1
 
-There are a few more rules for edge cases, and there are regional
-variants too.
+If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word.
 
-See <http://en.wikipedia.org/wiki/Pig_latin> for more details.
+For example:
+
+- `"apple"` -> `"appleay"` (starts with vowel)
+- `"xray"` -> `"xrayay"` (starts with `"xr"`)
+- `"yttria"` -> `"yttriaay"` (starts with `"yt"`)
+
+## Rule 2
+
+If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word.
+
+For example:
+
+- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant)
+- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants)
+- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants)
+
+## Rule 3
+
+If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word.
+
+For example:
+
+- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants)
+- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`")
+
+## Rule 4
+
+If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word.
+
+Some examples:
+
+- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`)
+- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`)
diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md
new file mode 100644
index 0000000..04baa47
--- /dev/null
+++ b/exercises/practice/pig-latin/.docs/introduction.md
@@ -0,0 +1,8 @@
+# Introduction
+
+Your parents have challenged you and your sibling to a game of two-on-two basketball.
+Confident they'll win, they let you score the first couple of points, but then start taking over the game.
+Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand.
+This will give you the edge to prevail over your parents!
+
+[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin
diff --git a/exercises/practice/pig-latin/.meta/config.json b/exercises/practice/pig-latin/.meta/config.json
index 628df60..2133c3d 100644
--- a/exercises/practice/pig-latin/.meta/config.json
+++ b/exercises/practice/pig-latin/.meta/config.json
@@ -20,7 +20,7 @@
       ".meta/src/example.clj"
     ]
   },
-  "blurb": "Implement a program that translates from English to Pig Latin",
+  "blurb": "Implement a program that translates from English to Pig Latin.",
   "source": "The Pig Latin exercise at Test First Teaching by Ultrasaurus",
   "source_url": "https://github.com/ultrasaurus/test-first-teaching/blob/master/learn_ruby/pig_latin/"
 }
diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md
index c211345..7dc34d2 100644
--- a/exercises/practice/protein-translation/.docs/instructions.md
+++ b/exercises/practice/protein-translation/.docs/instructions.md
@@ -11,7 +11,8 @@ Codons: `"AUG", "UUU", "UCU"`
 
 Protein: `"Methionine", "Phenylalanine", "Serine"`
 
-There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise.  If it works for one codon, the program should work for all of them.
+There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise.
+If it works for one codon, the program should work for all of them.
 However, feel free to expand the list in the test suite to include them all.
 
 There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated.
@@ -28,15 +29,17 @@ Note the stop codon `"UAA"` terminates the translation and the final methionine
 
 Below are the codons and resulting Amino Acids needed for the exercise.
 
-Codon                 | Protein
-:---                  | :---
-AUG                   | Methionine
-UUU, UUC              | Phenylalanine
-UUA, UUG              | Leucine
-UCU, UCC, UCA, UCG    | Serine
-UAU, UAC              | Tyrosine
-UGU, UGC              | Cysteine
-UGG                   | Tryptophan
-UAA, UAG, UGA         | STOP
-
-Learn more about [protein translation on Wikipedia](http://en.wikipedia.org/wiki/Translation_(biology))
+| Codon              | Protein       |
+| :----------------- | :------------ |
+| AUG                | Methionine    |
+| UUU, UUC           | Phenylalanine |
+| UUA, UUG           | Leucine       |
+| UCU, UCC, UCA, UCG | Serine        |
+| UAU, UAC           | Tyrosine      |
+| UGU, UGC           | Cysteine      |
+| UGG                | Tryptophan    |
+| UAA, UAG, UGA      | STOP          |
+
+Learn more about [protein translation on Wikipedia][protein-translation].
+
+[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology)
diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md
index 19cca8b..f23b5e2 100644
--- a/exercises/practice/space-age/.docs/instructions.md
+++ b/exercises/practice/space-age/.docs/instructions.md
@@ -1,18 +1,28 @@
 # Instructions
 
-Given an age in seconds, calculate how old someone would be on:
+Given an age in seconds, calculate how old someone would be on a planet in our Solar System.
 
-   - Mercury: orbital period 0.2408467 Earth years
-   - Venus: orbital period 0.61519726 Earth years
-   - Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds
-   - Mars: orbital period 1.8808158 Earth years
-   - Jupiter: orbital period 11.862615 Earth years
-   - Saturn: orbital period 29.447498 Earth years
-   - Uranus: orbital period 84.016846 Earth years
-   - Neptune: orbital period 164.79132 Earth years
+One Earth year equals 365.25 Earth days, or 31,557,600 seconds.
+If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years.
 
-So if you were told someone were 1,000,000,000 seconds old, you should
-be able to say that they're 31.69 Earth-years old.
+For the other planets, you have to account for their orbital period in Earth Years:
 
-If you're wondering why Pluto didn't make the cut, go watch [this
-youtube video](http://www.youtube.com/watch?v=Z_2gbGXzFbs).
+| Planet  | Orbital period in Earth Years |
+| ------- | ----------------------------- |
+| Mercury | 0.2408467                     |
+| Venus   | 0.61519726                    |
+| Earth   | 1.0                           |
+| Mars    | 1.8808158                     |
+| Jupiter | 11.862615                     |
+| Saturn  | 29.447498                     |
+| Uranus  | 84.016846                     |
+| Neptune | 164.79132                     |
+
+~~~~exercism/note
+The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year).
+The Gregorian calendar has, on average, 365.2425 days.
+While not entirely accurate, 365.25 is the value used in this exercise.
+See [Year on Wikipedia][year] for more ways to measure a year.
+
+[year]: https://en.wikipedia.org/wiki/Year#Summary
+~~~~
diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md
new file mode 100644
index 0000000..014d788
--- /dev/null
+++ b/exercises/practice/space-age/.docs/introduction.md
@@ -0,0 +1,20 @@
+# Introduction
+
+The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune).
+The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific).
+As you hand over the form to the customs officer, they scrutinize it and frown.
+"Do you _really_ expect me to believe you're just 50 years old?
+You must be closer to 200 years old!"
+
+Amused, you wait for the customs officer to start laughing, but they appear to be dead serious.
+You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_!
+As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years.
+After some quick calculations, you're able to provide your age in Mercury Years.
+The customs officer smiles, satisfied, and waves you through.
+You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups.
+
+~~~~exercism/note
+If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video].
+
+[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs
+~~~~
diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json
index 3e52d17..7294c9c 100644
--- a/exercises/practice/space-age/.meta/config.json
+++ b/exercises/practice/space-age/.meta/config.json
@@ -26,5 +26,5 @@
   },
   "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.",
   "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.",
-  "source_url": "http://pine.fm/LearnToProgram/?Chapter=01"
+  "source_url": "https://pine.fm/LearnToProgram/?Chapter=01"
 }
diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md
index af412dd..01e8a77 100644
--- a/exercises/practice/spiral-matrix/.docs/instructions.md
+++ b/exercises/practice/spiral-matrix/.docs/instructions.md
@@ -1,12 +1,11 @@
 # Instructions
 
-Given the size, return a square matrix of numbers in spiral order.
+Your task is to return a square matrix of a given size.
 
-The matrix should be filled with natural numbers, starting from 1
-in the top-left corner, increasing in an inward, clockwise spiral order,
-like these examples:
+The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples:
 
 ## Examples
+
 ### Spiral matrix of size 3
 
 ```text
diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md
new file mode 100644
index 0000000..25c7eb5
--- /dev/null
+++ b/exercises/practice/spiral-matrix/.docs/introduction.md
@@ -0,0 +1,11 @@
+# Introduction
+
+In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods.
+Despite numerous attempts, no one had ever succeeded in finding it.
+This was about to change, however, thanks to a young explorer named Elara.
+She had discovered an old document containing instructions on how to locate the treasure.
+Using these instructions, Elara was able to draw a map that revealed the path to the treasure.
+
+To her surprise, the path followed a peculiar clockwise spiral.
+It was no wonder no one had been able to find the treasure before!
+With the map in hand, Elara embarks on her journey to uncover the hidden treasure.
diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json
index 1ba77d1..2780de6 100644
--- a/exercises/practice/spiral-matrix/.meta/config.json
+++ b/exercises/practice/spiral-matrix/.meta/config.json
@@ -16,7 +16,7 @@
       ".meta/src/example.clj"
     ]
   },
-  "blurb": " Given the size, return a square matrix of numbers in spiral order.",
+  "blurb": "Given the size, return a square matrix of numbers in spiral order.",
   "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.",
-  "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/"
+  "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/"
 }
diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md
index d6e119e..519b7a6 100644
--- a/exercises/practice/yacht/.docs/instructions.md
+++ b/exercises/practice/yacht/.docs/instructions.md
@@ -1,35 +1,30 @@
-# Description
+# Instructions
 
-The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor.
-In the game, five dice are rolled and the result can be entered in any of twelve categories.
-The score of a throw of the dice depends on category chosen.
+Given five dice and a category, calculate the score of the dice for that category.
 
-## Scores in Yacht
-
-| Category | Score | Description | Example |
-| -------- | ----- | ----------- | ------- |
-| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 |
-| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 |
-| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 |
-| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 |
-| Fives | 5 × number of fives| Any combination | 5 1 5 2 5 scores 15 |
-| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 |
-| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 |
-| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 |
-| Little Straight |  30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 |
-| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 |
-| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 |
-| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 |
+~~~~exercism/note
+You'll always be presented with five dice.
+Each dice's value will be between one and six inclusively.
+The dice may be unordered.
+~~~~
 
-If the dice do not satisfy the requirements of a category, the score is zero.
-If, for example, *Four Of A Kind* is entered in the *Yacht* category, zero points are scored.
-A *Yacht* scores zero if entered in the *Full House* category.
-
-## Task
+## Scores in Yacht
 
-Given a list of values for five dice and a category, your solution should return the score of the dice for that category.
-If the dice do not satisfy the requirements of the category your solution should return 0.
-You can assume that five values will always be presented, and the value of each will be between one and six inclusively.
-You should not assume that the dice are ordered.
+| Category        | Score                  | Description                              | Example             |
+| --------------- | ---------------------- | ---------------------------------------- | ------------------- |
+| Ones            | 1 × number of ones     | Any combination                          | 1 1 1 4 5 scores 3  |
+| Twos            | 2 × number of twos     | Any combination                          | 2 2 3 4 5 scores 4  |
+| Threes          | 3 × number of threes   | Any combination                          | 3 3 3 3 3 scores 15 |
+| Fours           | 4 × number of fours    | Any combination                          | 1 2 3 3 5 scores 0  |
+| Fives           | 5 × number of fives    | Any combination                          | 5 1 5 2 5 scores 15 |
+| Sixes           | 6 × number of sixes    | Any combination                          | 2 3 4 5 6 scores 6  |
+| Full House      | Total of the dice      | Three of one number and two of another   | 3 3 3 5 5 scores 19 |
+| Four of a Kind  | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 |
+| Little Straight | 30 points              | 1-2-3-4-5                                | 1 2 3 4 5 scores 30 |
+| Big Straight    | 30 points              | 2-3-4-5-6                                | 2 3 4 5 6 scores 30 |
+| Choice          | Sum of the dice        | Any combination                          | 2 3 3 4 6 scores 18 |
+| Yacht           | 50 points              | All five dice showing the same face      | 4 4 4 4 4 scores 50 |
 
-[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game)
+If the dice do **not** satisfy the requirements of a category, the score is zero.
+If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored.
+A _Yacht_ scores zero if entered in the _Full House_ category.
diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md
new file mode 100644
index 0000000..5b541f5
--- /dev/null
+++ b/exercises/practice/yacht/.docs/introduction.md
@@ -0,0 +1,11 @@
+# Introduction
+
+Each year, something new is "all the rage" in your high school.
+This year it is a dice game: [Yacht][yacht].
+
+The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor.
+The game consists of twelve rounds.
+In each, five dice are rolled and the player chooses one of twelve categories.
+The chosen category is then used to score the throw of the dice.
+
+[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game)
diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json
index 85a212a..59e5d64 100644
--- a/exercises/practice/yacht/.meta/config.json
+++ b/exercises/practice/yacht/.meta/config.json
@@ -1,21 +1,20 @@
 {
-    "authors": [
-      "porkostomus"
+  "authors": [
+    "porkostomus"
+  ],
+  "contributors": [],
+  "files": {
+    "solution": [
+      "src/yacht.clj"
     ],
-    "contributors": [],
-    "files": {
-      "solution": [
-        "src/yacht.clj"
-      ],
-      "test": [
-        "test/yacht_test.clj"
-      ],
-      "example": [
-        ".meta/src/example.clj"
-      ]
-    },
-    "blurb": "Score a single throw of dice in the game Yacht",
-    "source": "James Kilfiger, using wikipedia",
-    "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)"
-  }
-  
\ No newline at end of file
+    "test": [
+      "test/yacht_test.clj"
+    ],
+    "example": [
+      ".meta/src/example.clj"
+    ]
+  },
+  "blurb": "Score a single throw of dice in the game Yacht.",
+  "source": "James Kilfiger, using wikipedia",
+  "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)"
+}


Test patch:
diff --git a/exercises/practice/allergies/.meta/tests.toml b/exercises/practice/allergies/.meta/tests.toml
index 8a754c2..799ab85 100644
--- a/exercises/practice/allergies/.meta/tests.toml
+++ b/exercises/practice/allergies/.meta/tests.toml
@@ -1,150 +1,160 @@
-# 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.
 
 [17fc7296-2440-4ac4-ad7b-d07c321bc5a0]
-description = "not allergic to anything"
+description = "testing for eggs allergy -> not allergic to anything"
 
 [07ced27b-1da5-4c2e-8ae2-cb2791437546]
-description = "allergic only to eggs"
+description = "testing for eggs allergy -> allergic only to eggs"
 
 [5035b954-b6fa-4b9b-a487-dae69d8c5f96]
-description = "allergic to eggs and something else"
+description = "testing for eggs allergy -> allergic to eggs and something else"
 
 [64a6a83a-5723-4b5b-a896-663307403310]
-description = "allergic to something, but not eggs"
+description = "testing for eggs allergy -> allergic to something, but not eggs"
 
 [90c8f484-456b-41c4-82ba-2d08d93231c6]
-description = "allergic to everything"
+description = "testing for eggs allergy -> allergic to everything"
 
 [d266a59a-fccc-413b-ac53-d57cb1f0db9d]
-description = "not allergic to anything"
+description = "testing for peanuts allergy -> not allergic to anything"
 
 [ea210a98-860d-46b2-a5bf-50d8995b3f2a]
-description = "allergic only to peanuts"
+description = "testing for peanuts allergy -> allergic only to peanuts"
 
 [eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b]
-description = "allergic to peanuts and something else"
+description = "testing for peanuts allergy -> allergic to peanuts and something else"
 
 [9152058c-ce39-4b16-9b1d-283ec6d25085]
-description = "allergic to something, but not peanuts"
+description = "testing for peanuts allergy -> allergic to something, but not peanuts"
 
 [d2d71fd8-63d5-40f9-a627-fbdaf88caeab]
-description = "allergic to everything"
+description = "testing for peanuts allergy -> allergic to everything"
 
 [b948b0a1-cbf7-4b28-a244-73ff56687c80]
-description = "not allergic to anything"
+description = "testing for shellfish allergy -> not allergic to anything"
 
 [9ce9a6f3-53e9-4923-85e0-73019047c567]
-description = "allergic only to shellfish"
+description = "testing for shellfish allergy -> allergic only to shellfish"
 
 [b272fca5-57ba-4b00-bd0c-43a737ab2131]
-description = "allergic to shellfish and something else"
+description = "testing for shellfish allergy -> allergic to shellfish and something else"
 
 [21ef8e17-c227-494e-8e78-470a1c59c3d8]
-description = "allergic to something, but not shellfish"
+description = "testing for shellfish allergy -> allergic to something, but not shellfish"
 
 [cc789c19-2b5e-4c67-b146-625dc8cfa34e]
-description = "allergic to everything"
+description = "testing for shellfish allergy -> allergic to everything"
 
 [651bde0a-2a74-46c4-ab55-02a0906ca2f5]
-description = "not allergic to anything"
+description = "testing for strawberries allergy -> not allergic to anything"
 
 [b649a750-9703-4f5f-b7f7-91da2c160ece]
-description = "allergic only to strawberries"
+description = "testing for strawberries allergy -> allergic only to strawberries"
 
 [50f5f8f3-3bac-47e6-8dba-2d94470a4bc6]
-description = "allergic to strawberries and something else"
+description = "testing for strawberries allergy -> allergic to strawberries and something else"
 
 [23dd6952-88c9-48d7-a7d5-5d0343deb18d]
-description = "allergic to something, but not strawberries"
+description = "testing for strawberries allergy -> allergic to something, but not strawberries"
 
 [74afaae2-13b6-43a2-837a-286cd42e7d7e]
-description = "allergic to everything"
+description = "testing for strawberries allergy -> allergic to everything"
 
 [c49a91ef-6252-415e-907e-a9d26ef61723]
-description = "not allergic to anything"
+description = "testing for tomatoes allergy -> not allergic to anything"
 
 [b69c5131-b7d0-41ad-a32c-e1b2cc632df8]
-description = "allergic only to tomatoes"
+description = "testing for tomatoes allergy -> allergic only to tomatoes"
 
 [1ca50eb1-f042-4ccf-9050-341521b929ec]
-description = "allergic to tomatoes and something else"
+description = "testing for tomatoes allergy -> allergic to tomatoes and something else"
 
 [e9846baa-456b-4eff-8025-034b9f77bd8e]
-description = "allergic to something, but not tomatoes"
+description = "testing for tomatoes allergy -> allergic to something, but not tomatoes"
 
 [b2414f01-f3ad-4965-8391-e65f54dad35f]
-description = "allergic to everything"
+description = "testing for tomatoes allergy -> allergic to everything"
 
 [978467ab-bda4-49f7-b004-1d011ead947c]
-description = "not allergic to anything"
+description = "testing for chocolate allergy -> not allergic to anything"
 
 [59cf4e49-06ea-4139-a2c1-d7aad28f8cbc]
-description = "allergic only to chocolate"
+description = "testing for chocolate allergy -> allergic only to chocolate"
 
 [b0a7c07b-2db7-4f73-a180-565e07040ef1]
-description = "allergic to chocolate and something else"
+description = "testing for chocolate allergy -> allergic to chocolate and something else"
 
 [f5506893-f1ae-482a-b516-7532ba5ca9d2]
-description = "allergic to something, but not chocolate"
+description = "testing for chocolate allergy -> allergic to something, but not chocolate"
 
 [02debb3d-d7e2-4376-a26b-3c974b6595c6]
-description = "allergic to everything"
+description = "testing for chocolate allergy -> allergic to everything"
 
 [17f4a42b-c91e-41b8-8a76-4797886c2d96]
-description = "not allergic to anything"
+description = "testing for pollen allergy -> not allergic to anything"
 
 [7696eba7-1837-4488-882a-14b7b4e3e399]
-description = "allergic only to pollen"
+description = "testing for pollen allergy -> allergic only to pollen"
 
 [9a49aec5-fa1f-405d-889e-4dfc420db2b6]
-description = "allergic to pollen and something else"
+description = "testing for pollen allergy -> allergic to pollen and something else"
 
 [3cb8e79f-d108-4712-b620-aa146b1954a9]
-description = "allergic to something, but not pollen"
+description = "testing for pollen allergy -> allergic to something, but not pollen"
 
 [1dc3fe57-7c68-4043-9d51-5457128744b2]
-description = "allergic to everything"
+description = "testing for pollen allergy -> allergic to everything"
 
 [d3f523d6-3d50-419b-a222-d4dfd62ce314]
-description = "not allergic to anything"
+description = "testing for cats allergy -> not allergic to anything"
 
 [eba541c3-c886-42d3-baef-c048cb7fcd8f]
-description = "allergic only to cats"
+description = "testing for cats allergy -> allergic only to cats"
 
 [ba718376-26e0-40b7-bbbe-060287637ea5]
-description = "allergic to cats and something else"
+description = "testing for cats allergy -> allergic to cats and something else"
 
 [3c6dbf4a-5277-436f-8b88-15a206f2d6c4]
-description = "allergic to something, but not cats"
+description = "testing for cats allergy -> allergic to something, but not cats"
 
 [1faabb05-2b98-4995-9046-d83e4a48a7c1]
-description = "allergic to everything"
+description = "testing for cats allergy -> allergic to everything"
 
 [f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f]
-description = "no allergies"
+description = "list when: -> no allergies"
 
 [9e1a4364-09a6-4d94-990f-541a94a4c1e8]
-description = "just eggs"
+description = "list when: -> just eggs"
 
 [8851c973-805e-4283-9e01-d0c0da0e4695]
-description = "just peanuts"
+description = "list when: -> just peanuts"
 
 [2c8943cb-005e-435f-ae11-3e8fb558ea98]
-description = "just strawberries"
+description = "list when: -> just strawberries"
 
 [6fa95d26-044c-48a9-8a7b-9ee46ec32c5c]
-description = "eggs and peanuts"
+description = "list when: -> eggs and peanuts"
 
 [19890e22-f63f-4c5c-a9fb-fb6eacddfe8e]
-description = "more than eggs but not peanuts"
+description = "list when: -> more than eggs but not peanuts"
 
 [4b68f470-067c-44e4-889f-c9fe28917d2f]
-description = "lots of stuff"
+description = "list when: -> lots of stuff"
 
 [0881b7c5-9efa-4530-91bd-68370d054bc7]
-description = "everything"
+description = "list when: -> everything"
 
 [12ce86de-b347-42a0-ab7c-2e0570f0c65b]
-description = "no allergen score parts"
+description = "list when: -> no allergen score parts"
+
+[93c2df3e-4f55-4fed-8116-7513092819cd]
+description = "list when: -> no allergen score parts without highest valid score"
diff --git a/exercises/practice/allergies/test/allergies_test.clj b/exercises/practice/allergies/test/allergies_test.clj
index cda4aae..7d1acd8 100644
--- a/exercises/practice/allergies/test/allergies_test.clj
+++ b/exercises/practice/allergies/test/allergies_test.clj
@@ -1,49 +1,124 @@
 (ns allergies-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest testing is]]
             allergies))
 
-(deftest no-allergies-at-all
-  (is (= [] (allergies/allergies 0))))
+(deftest testing-for-eggs-allergy
+  (testing "Testing for eggs allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :eggs))))
+  (testing "Testing for eggs allergy -> allergic only to eggs"
+    (is (true? (allergies/allergic-to? 1 :eggs))))
+  (testing "Testing for eggs allergy -> allergic to eggs and something else"
+    (is (true? (allergies/allergic-to? 3 :eggs))))
+  (testing "Testing for eggs allergy -> allergic to something, but not eggs"
+    (is (false? (allergies/allergic-to? 2 :eggs))))
+  (testing "Testing for eggs allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :eggs)))))
 
-(deftest allergic-to-just-eggs
-  (is (= [:eggs] (allergies/allergies 1))))
+(deftest testing-for-peanuts-allergy
+  (testing "Testing for peanuts allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :peanuts))))
+  (testing "Testing for peanuts allergy -> allergic only to peanuts"
+    (is (true? (allergies/allergic-to? 2 :peanuts))))
+  (testing "Testing for peanuts allergy -> allergic to peanuts and something else"
+    (is (true? (allergies/allergic-to? 7 :peanuts))))
+  (testing "Testing for peanuts allergy -> allergic to something, but not peanuts"
+    (is (false? (allergies/allergic-to? 5 :peanuts))))
+  (testing "Testing for peanuts allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :peanuts)))))
 
-(deftest allergic-to-just-peanuts
-  (is (= [:peanuts] (allergies/allergies 2))))
+(deftest testing-for-shellfish-allergy
+  (testing "Testing for shellfish allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :shellfish))))
+  (testing "Testing for shellfish allergy -> allergic only to shellfish"
+    (is (true? (allergies/allergic-to? 4 :shellfish))))
+  (testing "Testing for shellfish allergy -> allergic to shellfish and something else"
+    (is (true? (allergies/allergic-to? 14 :shellfish))))
+  (testing "Testing for shellfish allergy -> allergic to something, but not shellfish"
+    (is (false? (allergies/allergic-to? 10 :shellfish))))
+  (testing "Testing for shellfish allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :shellfish)))))
 
-(deftest allergic-to-just-strawberries
-  (is (= [:strawberries] (allergies/allergies 8))))
+(deftest testing-for-strawberries-allergy
+  (testing "Testing for strawberries allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :strawberries))))
+  (testing "Testing for strawberries allergy -> allergic only to strawberries"
+    (is (true? (allergies/allergic-to? 8 :strawberries))))
+  (testing "Testing for strawberries allergy -> allergic to strawberries and something else"
+    (is (true? (allergies/allergic-to? 28 :strawberries))))
+  (testing "Testing for strawberries allergy -> allergic to something, but not strawberries"
+    (is (false? (allergies/allergic-to? 20 :strawberries))))
+  (testing "Testing for strawberries allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :strawberries)))))
 
-(deftest allergic-to-eggs-and-peanuts
-  (is (= [:eggs :peanuts] (allergies/allergies 3))))
+(deftest testing-for-tomatoes-allergy
+  (testing "Testing for tomatoes allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :tomatoes))))
+  (testing "Testing for tomatoes allergy -> allergic only to tomatoes"
+    (is (true? (allergies/allergic-to? 16 :tomatoes))))
+  (testing "Testing for tomatoes allergy -> allergic to tomatoes and something else"
+    (is (true? (allergies/allergic-to? 56 :tomatoes))))
+  (testing "Testing for tomatoes allergy -> allergic to something, but not tomatoes"
+    (is (false? (allergies/allergic-to? 40 :tomatoes))))
+  (testing "Testing for tomatoes allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :tomatoes)))))
 
-(deftest allergic-to-more-than-eggs-but-not-peanuts
-  (is (= [:eggs :shellfish] (allergies/allergies 5))))
+(deftest testing-for-chocolate-allergy
+  (testing "Testing for chocolate allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :chocolate))))
+  (testing "Testing for chocolate allergy -> allergic only to chocolate"
+    (is (true? (allergies/allergic-to? 32 :chocolate))))
+  (testing "Testing for chocolate allergy -> allergic to chocolate and something else"
+    (is (true? (allergies/allergic-to? 112 :chocolate))))
+  (testing "Testing for chocolate allergy -> allergic to something, but not chocolate"
+    (is (false? (allergies/allergic-to? 80 :chocolate))))
+  (testing "Testing for chocolate allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :chocolate)))))
 
-(deftest allergic-to-lots-of-stuff
-  (is (= [:strawberries :tomatoes :chocolate :pollen :cats]
-         (allergies/allergies 248))))
+(deftest testing-for-pollen-allergy
+  (testing "Testing for pollen allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :pollen))))
+  (testing "Testing for pollen allergy -> allergic only to pollen"
+    (is (true? (allergies/allergic-to? 64 :pollen))))
+  (testing "Testing for pollen allergy -> allergic to pollen and something else"
+    (is (true? (allergies/allergic-to? 224 :pollen))))
+  (testing "Testing for pollen allergy -> allergic to something, but not pollen"
+    (is (false? (allergies/allergic-to? 160 :pollen))))
+  (testing "Testing for pollen allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :pollen)))))
 
-(deftest allergic-to-everything
-  (is (= [:eggs :peanuts :shellfish :strawberries
-          :tomatoes :chocolate :pollen :cats]
-         (allergies/allergies 255))))
+(deftest testing-for-cats-allergy
+  (testing "Testing for cats allergy -> not allergic to anything"
+    (is (false? (allergies/allergic-to? 0 :cats))))
+  (testing "Testing for cats allergy -> allergic only to cats"
+    (is (true? (allergies/allergic-to? 128 :cats))))
+  (testing "Testing for cats allergy -> allergic to cats and something else"
+    (is (true? (allergies/allergic-to? 192 :cats))))
+  (testing "Testing for cats allergy -> allergic to something, but not cats"
+    (is (false? (allergies/allergic-to? 64 :cats))))
+  (testing "Testing for cats allergy -> allergic to everything"
+    (is (true? (allergies/allergic-to? 255 :cats)))))
 
-(deftest no-allergies-means-not-allergic-peanuts
-  (is (not (allergies/allergic-to? 0 :peanuts))))
-
-(deftest no-allergies-means-not-allergic-cats
-  (is (not (allergies/allergic-to? 0 :cats))))
-
-(deftest no-allergies-means-not-allergic-strawberries
-  (is (not (allergies/allergic-to? 0 :strawberries))))
-
-(deftest is-allergic-to-eggs
-  (is (allergies/allergic-to? 1 :eggs)))
-
-(deftest allergic-to-eggs-in-addition-to-other-stuff
-  (is (allergies/allergic-to? 5 :eggs)))
-
-(deftest ignore-non-allergen-score-parts
-  (is (= [:eggs :shellfish :strawberries :tomatoes :chocolate :pollen :cats]
-         (allergies/allergies 509))))
+(deftest list-when
+  (testing "List when: no allergies"
+    (is (= [] (allergies/allergies 0))))
+  (testing "List when: allergic to just eggs"
+    (is (= [:eggs] (allergies/allergies 1))))
+  (testing "List when: allergic to just peanuts"
+    (is (= [:peanuts] (allergies/allergies 2))))
+  (testing "List when: allergic to just strawberries"
+    (is (= [:strawberries] (allergies/allergies 8))))
+  (testing "List when: allergic to eggs and peanuts"
+    (is (= [:eggs :peanuts] (allergies/allergies 3))))
+  (testing "List when: allergic to more than eggs but not peanuts"
+    (is (= [:eggs :shellfish] (allergies/allergies 5))))
+  (testing "List when: allergic to lots of stuff"
+    (is (= [:strawberries :tomatoes :chocolate :pollen :cats]
+           (allergies/allergies 248))))
+  (testing "List when: allergic to everything"
+    (is (= [:eggs :peanuts :shellfish :strawberries :tomatoes :chocolate :pollen :cats]
+           (allergies/allergies 255))))
+  (testing "List when: no allergen score parts"
+    (is (= [:eggs :shellfish :strawberries :tomatoes :chocolate :pollen :cats]
+           (allergies/allergies 509))))
+  (testing "List when: no allergen score parts without highest valid score"
+    (is (= [:eggs] (allergies/allergies 257)))))
diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml
index 7fb93f7..4d90562 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"
@@ -34,12 +46,41 @@ description = "detects anagrams using case-insensitive possible matches"
 
 [7cc195ad-e3c7-44ee-9fd2-d3c344806a2c]
 description = "does not detect an anagram if the original word is repeated"
+include = false
+
+[630abb71-a94e-4715-8395-179ec1df9f91]
+description = "does not detect an anagram if the original word is repeated"
+reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c"
 
 [9878a1c9-d6ea-4235-ae51-3ea2befd6842]
 description = "anagrams must use all letters exactly once"
 
 [85757361-4535-45fd-ac0e-3810d40debc1]
 description = "words are not anagrams of themselves (case-insensitive)"
+include = false
+
+[68934ed0-010b-4ef9-857a-20c9012d1ebf]
+description = "words are not anagrams of themselves"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e]
+description = "words are not anagrams of themselves even if letter case is partially different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[ba53e423-7e02-41ee-9ae2-71f91e6d18e6]
+description = "words are not anagrams of themselves even if letter case is completely different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
 
 [a0705568-628c-4b55-9798-82e4acde51ca]
 description = "words other than themselves can be anagrams"
+include = false
+
+[33d3f67e-fbb9-49d3-a90e-0beb00861da7]
+description = "words other than themselves can be anagrams"
+reimplements = "a0705568-628c-4b55-9798-82e4acde51ca"
+
+[a6854f66-eec1-4afd-a137-62ef2870c051]
+description = "handles case of greek letters"
+
+[fd3509e5-e3ba-409d-ac3d-a9ac84d13296]
+description = "different characters may have the same bytes"
diff --git a/exercises/practice/anagram/test/anagram_test.clj b/exercises/practice/anagram/test/anagram_test.clj
index 17b5703..d8c1d53 100644
--- a/exercises/practice/anagram/test/anagram_test.clj
+++ b/exercises/practice/anagram/test/anagram_test.clj
@@ -1,38 +1,93 @@
 (ns anagram-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest testing is]]
             anagram))
 
 (deftest no-matches
-  (is (= []
-         (anagram/anagrams-for "diaper" ["hello" "world" "zombies" "pants"]))))
+  (testing "No matches"
+    (is (= []
+           (anagram/anagrams-for "diaper" ["hello" "world" "zombies" "pants"])))))
 
-(deftest detect-simple-anagram
-  (is (= ["tan"] (anagram/anagrams-for "ant" ["tan" "stand" "at"]))))
+(deftest detects-two-anagrams
+  (testing "Detects two anagrams"
+    (is (= ["lemons", "melons"]
+           (anagram/anagrams-for "solemn" ["lemons", "cherry", "melons"])))))
 
-(deftest does-not-confuse-different-duplicates
-  (is (= [] (anagram/anagrams-for "galea" ["eagle"]))))
+(deftest does-not-detect-anagram-subsets
+  (testing "Does not detect anagram subsets"
+    (is (= []
+           (anagram/anagrams-for "good" ["dog", "goody"])))))
 
-(deftest eliminate-anagram-subsets
-  (is (= [] (anagram/anagrams-for "good" ["dog" "goody"]))))
+(deftest detects-anagram
+  (testing "Detects anagram"
+    (is (= ["inlets"]
+           (anagram/anagrams-for "listen" ["enlists", "google", "inlets", "banana"])))))
 
-(deftest detect-anagram
-  (is (= ["inlets"]
-         (let [coll ["enlists" "google" "inlets" "banana"]]
-           (anagram/anagrams-for "listen" coll)))))
+(deftest detects-three-anagrams
+  (testing "Detects three anagrams"
+    (is (= ["gallery", "regally", "largely"]
+           (anagram/anagrams-for "allergy" ["gallery", "ballerina", "regally", "clergy", "largely", "leading"])))))
 
-(deftest multiple-anagrams
-  (is (= ["gallery" "regally" "largely"]
-         (let [coll ["gallery" "ballerina" "regally"
-                     "clergy"  "largely"   "leading"]]
-           (anagram/anagrams-for "allergy" coll)))))
+(deftest detects-multiple-anagrams-with-different-case
+  (testing "Detects multiple anagrams with different case"
+    (is (= ["Eons", "ONES"]
+           (anagram/anagrams-for "nose" ["Eons", "ONES"])))))
 
-(deftest case-insensitive-anagrams
-  (is (= ["Carthorse"]
-         (let [coll ["cashregister" "Carthorse" "radishes"]]
-           (anagram/anagrams-for "Orchestra" coll)))))
+(deftest does-not-detect-non-anagrams-with-identical-checksum
+  (testing "Does not detect non-anagrams with identical checksum"
+    (is (= []
+           (anagram/anagrams-for "mass" ["last"])))))
 
-(deftest word-is-not-own-anagram
-  (is (= [] (anagram/anagrams-for "banana" ["banana"]))))
+(deftest detects-anagrams-case-insensitively
+  (testing "Detects anagrams case-insensitively"
+    (is (= ["Carthorse"]
+           (anagram/anagrams-for "Orchestra" ["cashregister", "Carthorse", "radishes"])))))
 
-(deftest capital-word-is-not-own-anagram
-  (is (= [] (anagram/anagrams-for "BANANA" ["banana"]))))
+(deftest detects-anagrams-using-case-insensitive-subject
+  (testing "Detects anagrams using case-insensitive subject"
+    (is (= ["carthorse"]
+           (anagram/anagrams-for "Orchestra" ["cashregister", "carthorse", "radishes"])))))
+
+(deftest detects-anagrams-using-case-insensitive-possible-matches
+  (testing "Detects anagrams using case-insensitive possible matches"
+    (is (= ["Carthorse"]
+           (anagram/anagrams-for "orchestra" ["cashregister", "Carthorse", "radishes"])))))
+
+(deftest does-not-detect-anagram-if-original-word-is-repeated
+  (testing "Does not detect an anagram if the original word is repeated"
+    (is (= []
+           (anagram/anagrams-for "go" ["goGoGO"])))))
+
+(deftest anagrams-must-use-all-letters-exactly-once
+  (testing "Anagrams must use all letters exactly once"
+    (is (= []
+           (anagram/anagrams-for "tapper" ["patter"])))))
+
+(deftest words-are-not-anagrams-of-themselves
+  (testing "Words are not anagrams of themselves"
+    (is (= []
+           (anagram/anagrams-for "BANANA" ["BANANA"])))))
+
+(deftest words-are-not-anagrams-of-themselves-even-if-letter-case-is-partially-different
+  (testing "Words are not anagrams of themselves even if letter case is partially different"
+    (is (= []
+           (anagram/anagrams-for "BANANA" ["Banana"])))))
+
+(deftest words-are-not-anagrams-of-themselves-even-if-letter-case-is-completely-different
+  (testing "Words are not anagrams of themselves even if letter case is completely different"
+    (is (= []
+           (anagram/anagrams-for "BANANA" ["banana"])))))
+
+(deftest words-other-than-themselves-can-be-anagrams
+  (testing "Words other than themselves can be anagrams"
+    (is (= ["Silent"]
+           (anagram/anagrams-for "LISTEN" ["LISTEN", "Silent"])))))
+
+(deftest handles-greek-letters
+  (testing "Handles case of greek letters"
+    (is (= ["ΒΓΑ", "γβα"]
+           (anagram/anagrams-for "ΑΒΓ" ["ΒΓΑ", "ΒΓΔ", "γβα", "αβγ"])))))
+
+(deftest different-characters-may-have-same-bytes
+  (testing "Different characters may have the same bytes"
+    (is (= []
+           (anagram/anagrams-for "a⬂" ["€a"])))))
diff --git a/exercises/practice/binary-search/test/binary_search_test.clj b/exercises/practice/binary-search/test/binary_search_test.clj
index 00efed5..60f8464 100644
--- a/exercises/practice/binary-search/test/binary_search_test.clj
+++ b/exercises/practice/binary-search/test/binary_search_test.clj
@@ -1,40 +1,52 @@
 (ns binary-search-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest is testing]]
             binary-search))
 
-(def short-vector [1, 3, 4, 6, 8, 9, 11])
-
-(def large-vector [1, 3, 5, 8, 13, 21, 34, 55, 89])
-
-(def even-length-vector [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377])
-
-(deftest it-finds-position-of-middle-item
-  (is (= 3 (binary-search/middle short-vector))))
-
-(deftest searches-a-singleton
-  (is (= 0 (binary-search/search-for 4 [4]))))
-
-(deftest it-finds-position-of-search-data
-  (is (= 5 (binary-search/search-for 9 short-vector))))
-
-(deftest it-finds-position-in-a-larger-list
-  (is (= 1 (binary-search/search-for 3 large-vector))))
-
-(deftest it-finds-position-in-a-larger-list-again
-  (is (= 7 (binary-search/search-for 55 large-vector))))
-
-(deftest it-finds-correct-position-in-a-list-with-an-even-number-of-elements
-  (is (= 5 (binary-search/search-for 21 even-length-vector))))
-
-(deftest it-finds-correct-position-in-a-list-with-an-even-number-of-elements-again
-  (is (= 6 (binary-search/search-for 34 even-length-vector))))
-
-(deftest it-works-on-lists
-  (is (= 7 (binary-search/search-for 7 (range 10)))))
-
-(deftest it-works-on-lists-again
-  (is (= 4 (binary-search/search-for 3 '(-3 -2 0 1 3 4)))))
-
-(deftest throws-exception-when-element-not-found
-  (is (thrown-with-msg? Throwable #"not found"
-                        (binary-search/search-for 20 short-vector))))
+(deftest finds-value-in-array-with-one-element
+  (testing "Finds a value in an array with one element"
+           (is (= 0 (binary-search/search-for 6 [6])))))
+
+(deftest finds-value-in-middle-of-array
+  (testing "Finds a value in the middle of an array"
+    (is (= 3 (binary-search/search-for 6 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest finds-value-at-beginning-of-array
+  (testing "Finds a value at the beginning of an array"
+    (is (= 0 (binary-search/search-for 1 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest finds-value-at-end-of-array
+  (testing "Finds a value at the end of an array"
+    (is (= 6 (binary-search/search-for 11 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest finds-value-in-array-of-odd-length
+  (testing "Finds a value in an array of odd length"
+    (is (= 9 (binary-search/search-for 144 [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634])))))
+
+(deftest finds-value-in-array-of-even-length
+  (testing "Finds a value in an array of even length"
+    (is (= 5 (binary-search/search-for 21 [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377])))))
+
+(deftest identifies-that-value-is-not-in-array
+  (testing "Identifies that a value is not included in the array"
+    (is (thrown-with-msg? Throwable #"not found"
+                          (binary-search/search-for 7 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest value-smaller-than-arrays-smallest-value-not-found
+  (testing "A value smaller than the array's smallest value is not found"
+    (is (thrown-with-msg? Throwable #"not found"
+                          (binary-search/search-for 0 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest value-larger-than-arrays-largest-value-not-found
+  (testing "A value larger than the array's largest value is not found"
+    (is (thrown-with-msg? Throwable #"not found"
+                          (binary-search/search-for 13 [1, 3, 4, 6, 8, 9, 11])))))
+
+(deftest nothing-found-in-empty-array
+  (testing "Nothing is found in an empty array"
+    (is (thrown-with-msg? Throwable #"not found"
+                          (binary-search/search-for 1 [])))))
+
+(deftest nothing-found-when-left-and-right-bounds-cross
+  (testing "Nothing is found when the left and right bounds cross"
+    (is (thrown-with-msg? Throwable #"not found"
+                          (binary-search/search-for 0 [1, 2])))))
diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml
index 9bb8224..c0be0c4 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"
@@ -26,6 +33,9 @@ description = "invalid credit card"
 [20e67fad-2121-43ed-99a8-14b5b856adb9]
 description = "invalid long number with an even remainder"
 
+[7e7c9fc1-d994-457c-811e-d390d52fba5e]
+description = "invalid long number with a remainder divisible by 5"
+
 [ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa]
 description = "valid number with an even number of digits"
 
@@ -50,8 +60,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/test/luhn_test.clj b/exercises/practice/luhn/test/luhn_test.clj
index d706786..892c03f 100644
--- a/exercises/practice/luhn/test/luhn_test.clj
+++ b/exercises/practice/luhn/test/luhn_test.clj
@@ -2,41 +2,90 @@
   (:require [clojure.test :refer [deftest is testing]]
             luhn))
 
-(deftest single-digit-strings
-    (is (false? (luhn/valid? "1"))))
+(deftest single-digit-strings-are-invalid
+  (testing "Single digit strings can not be valid"
+    (is (false? (luhn/valid? "1")))))
 
-(deftest single-zero
-    (is (false? (luhn/valid? "0"))))
+(deftest single-zero-is-invalid
+  (testing "A single zero is invalid"
+    (is (false? (luhn/valid? "0")))))
 
-(deftest sin
-    (is (true? (luhn/valid? " 5 9 "))))
+(deftest simple-valid-SIN-that-remains-valid-if-reversed
+  (testing "A simple valid SIN that remains valid if reversed"
+    (is (true? (luhn/valid? "059")))))
 
-(deftest Canadian-SIN
-    (is (true? (luhn/valid? "046 454 286"))))
+(deftest simple-valid-SIN-that-becomes-invalid-if-reversed
+  (testing "A simple valid SIN that becomes invalid if reversed"
+    (is (true? (luhn/valid? "59")))))
 
-(deftest invalid-Canadian-SIN
-    (is (false? (luhn/valid? "046 454 287"))))
+(deftest valid-canadian-SIN
+  (testing "A valid Canadian SIN"
+    (is (true? (luhn/valid? "055 444 285")))))
+
+(deftest invalid-canadian-SIN
+  (testing "Invalid Canadian SIN"
+    (is (false? (luhn/valid? "055 444 286")))))
 
 (deftest invalid-credit-card
-    (is (false? (luhn/valid? "8273 1232 7352 0569"))))
+  (testing "Invalid credit card"
+    (is (false? (luhn/valid? "8273 1232 7352 0569")))))
+
+(deftest invalid-long-number-with-an-even-remainder
+  (testing "Invalid long number with an even remainder"
+    (is (false? (luhn/valid? "1 2345 6789 1234 5678 9012")))))
+
+(deftest invalid-long-number-with-a-remainder-divisible-by-5
+  (testing "Invalid long number with a remainder divisible by 5"
+    (is (false? (luhn/valid? "1 2345 6789 1234 5678 9013")))))
+
+(deftest valid-number-with-an-even-number-of-digits
+  (testing "Valid number with an even number of digits"
+    (is (true? (luhn/valid? "095 245 88")))))
+
+(deftest valid-number-with-an-odd-number-of-spaces
+  (testing "Valid number with an odd number of spaces"
+    (is (true? (luhn/valid? "234 567 891 234")))))
+
+(deftest valid-strings-with-a-non-digit-added-at-the-end-become-invalid
+  (testing "Valid strings with a non-digit added at the end become invalid"
+    (is (false? (luhn/valid? "059a")))))
+
+(deftest valid-strings-with-punctuation-included-become-invalid
+  (testing "Valid strings with punctuation included become invalid"
+    (is (false? (luhn/valid? "055-444-285")))))
+
+(deftest valid-strings-with-symbols-included-become-invalid
+  (testing "Valid strings with symbols included become invalid"
+    (is (false? (luhn/valid? "055# 444$ 285")))))
+
+(deftest single-zero-with-space-is-invalid
+  (testing "Single zero with space is invalid"
+    (is (false? (luhn/valid? " 0")))))
 
-(deftest non-digit-added
-    (is (false? (luhn/valid? "046a 454 286"))))
+(deftest more-than-a-single-zero-is-valid
+  (testing "More than a single zero is valid"
+    (is (true? (luhn/valid? "0000 0")))))
 
-(deftest punctuation
-    (is (false? (luhn/valid? "055-444-285"))))
+(deftest input-digit-9-is-correctly-converted-to-output-digit-9
+  (testing "Input digit 9 is correctly converted to output digit 9"
+    (is (true? (luhn/valid? "091")))))
 
-(deftest symbols
-    (is (false? (luhn/valid? "055£ 444$ 285"))))
+(deftest very-long-input-is-valid
+  (testing "Very long input is valid"
+    (is (true? (luhn/valid? "9999999999 9999999999 9999999999 9999999999")))))
 
-(deftest single-zero-with-space
-    (is (false? (luhn/valid? " 0"))))
+(deftest valid-luhn-with-an-odd-number-of-digits-and-non-zero-first-digit
+  (testing "Valid luhn with an odd number of digits and non zero first digit"
+    (is (true? (luhn/valid? "109")))))
 
-(deftest lots-of-zeros
-    (is (true? (luhn/valid? " 00000"))))
+(deftest using-ascii-value-for-non-doubled-non-digit-is-not-allowed
+  (testing "Using ascii value for non-doubled non-digit isn't allowed"
+    (is (false? (luhn/valid? "055b 444 285")))))
 
-(deftest another-valid-sin
-    (is (true? (luhn/valid? "055 444 285"))))
+(deftest using-ascii-value-for-doubled-non-digit-is-not-allowed
+  (testing "Using ascii value for doubled non-digit isn't allowed"
+    (is (false? (luhn/valid? ":9")))))
 
-(deftest nine-doubled
-    (is (true? (luhn/valid? "091"))))
+(deftest non-numeric-non-space-char-in-middle-with-sum-that-is-divisible-by-10-is-not-allowed
+  (testing "Non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed"
+    (is (false? (luhn/valid? "59%59")))))
diff --git a/exercises/practice/matching-brackets/.meta/tests.toml b/exercises/practice/matching-brackets/.meta/tests.toml
index cc9e471..35a98a0 100644
--- a/exercises/practice/matching-brackets/.meta/tests.toml
+++ b/exercises/practice/matching-brackets/.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.
 
 [81ec11da-38dd-442a-bcf9-3de7754609a5]
 description = "paired square brackets"
@@ -41,12 +48,21 @@ description = "unpaired and nested brackets"
 [a0205e34-c2ac-49e6-a88a-899508d7d68e]
 description = "paired and wrong nested brackets"
 
+[1d5c093f-fc84-41fb-8c2a-e052f9581602]
+description = "paired and wrong nested brackets but innermost are correct"
+
 [ef47c21b-bcfd-4998-844c-7ad5daad90a8]
 description = "paired and incomplete brackets"
 
 [a4675a40-a8be-4fc2-bc47-2a282ce6edbe]
 description = "too many closing brackets"
 
+[a345a753-d889-4b7e-99ae-34ac85910d1a]
+description = "early unexpected brackets"
+
+[21f81d61-1608-465a-b850-baa44c5def83]
+description = "early mismatched brackets"
+
 [99255f93-261b-4435-a352-02bdecc9bdf2]
 description = "math expression"
 
diff --git a/exercises/practice/matching-brackets/test/matching_brackets_test.clj b/exercises/practice/matching-brackets/test/matching_brackets_test.clj
index d84e5c7..21c7e86 100644
--- a/exercises/practice/matching-brackets/test/matching_brackets_test.clj
+++ b/exercises/practice/matching-brackets/test/matching_brackets_test.clj
@@ -1,50 +1,83 @@
 (ns matching-brackets-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest testing is]]
             matching-brackets))
 
-(deftest paired-square
-  (is (matching-brackets/valid? "[]") "paired square brackets"))
+(deftest paired-square-brackets
+  (testing "paired square brackets"
+    (is (matching-brackets/valid? "[]"))))
 
 (deftest empty-string
-  (is (matching-brackets/valid? "") "empty string"))
+  (testing "Empty string"
+    (is (matching-brackets/valid? ""))))
 
 (deftest unpaired-brackets
-  (is (false? (matching-brackets/valid? "[[")) "unpaired brackets"))
+  (testing "Unpaired brackets"
+    (is (false? (matching-brackets/valid? "[[")))))
 
 (deftest wrong-ordered-brackets
-  (is (false? (matching-brackets/valid? "}{")) "wrong ordered brackets"))
+  (testing "Wrong ordered brackets"
+    (is (false? (matching-brackets/valid? "}{")))))
+
+(deftest wrong-closing-bracket
+  (testing "Wrong closing bracket"
+    (is (false? (matching-brackets/valid? "{]")))))
 
 (deftest paired-with-whitespace
-  (is (matching-brackets/valid? "{ }") "paired with whitespace"))
+  (testing "Paired with whitespace"
+    (is (matching-brackets/valid? "{ }"))))
+
+(deftest partially-paired-brackets
+  (testing "Partially paired brackets"
+    (is (false? (matching-brackets/valid? "{[])")))))
 
 (deftest simple-nested-brackets
-  (is (matching-brackets/valid? "{[]}")
-      "simple nested brackets"))
+  (testing "Simple nested brackets"
+    (is (matching-brackets/valid? "{[]}"))))
 
 (deftest several-paired-brackets
-  (is (matching-brackets/valid? "{}[]")
-      "several paired brackets"))
+  (testing "Several paired brackets"
+    (is (matching-brackets/valid? "{}[]"))))
 
 (deftest paired-and-nested-brackets
-  (is (matching-brackets/valid? "([{}({}[])])")
-      "paired and nested brackets"))
+  (testing "Paired and nested brackets"
+    (is (matching-brackets/valid? "([{}({}[])])"))))
 
 (deftest unopened-closing-brackets
-  (is (false? (matching-brackets/valid? "{[)][]}"))
-      "unopened closing brackets"))
+  (testing "Unopened closing brackets"
+    (is (false? (matching-brackets/valid? "{[)][]}")))))
  
 (deftest unpaired-and-nested-brackets
-  (is (false? (matching-brackets/valid? "([{])"))
-      "unpaired and nested brackets"))
+  (testing "Unpaired and nested brackets"
+    (is (false? (matching-brackets/valid? "([{])")))))
 
 (deftest paired-and-wrong-nested-brackets
-  (is (false? (matching-brackets/valid? "[({]})"))
-      "paired and wrong nested brackets"))
+  (testing "Paired and wrong nested brackets"
+    (is (false? (matching-brackets/valid? "[({]})")))))
+
+(deftest paired-and-wrong-nested-brackets-but-innermost-are-correct
+  (testing "Paired and wrong nested brackets but innermost are correct"
+    (is (false? (matching-brackets/valid? "[({}])")))))
+
+(deftest paired-and-incomplete-brackets
+  (testing "Paired and incomplete brackets"
+    (is (false? (matching-brackets/valid? "{}[")))))
+
+(deftest too-many-closing-brackets
+  (testing "Too many closing brackets"
+    (is (false? (matching-brackets/valid? "[]]")))))
+
+(deftest early-unexpected-brackets
+  (testing "Early unexpected brackets"
+    (is (false? (matching-brackets/valid? ")()")))))
+
+(deftest early-mismatched-brackets
+  (testing "Early mismatched brackets"
+    (is (false? (matching-brackets/valid? "{)()")))))
 
 (deftest math-expression
-  (is (matching-brackets/valid? "(((185 + 223.85) * 15) - 543)/2")
-      "math expression"))
+  (testing "Math expression"
+    (is (matching-brackets/valid? "(((185 + 223.85) * 15) - 543)/2"))))
 
 (deftest complex-latex-expression
-  (is (matching-brackets/valid? "\\\\left(\\\\begin{array}{cc} \\\\frac{1}{3} & x\\\\\\\\ \\\\mathrm{e}^{x} &... x^2 \\\\end{array}\\\\right)")
-      "complex latex expression"))
+  (testing "Complex latex expression"
+    (is (matching-brackets/valid? "\\\\left(\\\\begin{array}{cc} \\\\frac{1}{3} & x\\\\\\\\ \\\\mathrm{e}^{x} &... x^2 \\\\end{array}\\\\right)"))))
diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml
index 02a54c3..10ee1ee 100644
--- a/exercises/practice/protein-translation/.meta/tests.toml
+++ b/exercises/practice/protein-translation/.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.
+
+[2c44f7bf-ba20-43f7-a3bf-f2219c0c3f98]
+description = "Empty RNA sequence results in no proteins"
 
 [96d3d44f-34a2-4db4-84cd-fff523e069be]
 description = "Methionine RNA sequence"
@@ -53,6 +63,12 @@ description = "STOP codon RNA sequence 2"
 [9c2ad527-ebc9-4ace-808b-2b6447cb54cb]
 description = "STOP codon RNA sequence 3"
 
+[f4d9d8ee-00a8-47bf-a1e3-1641d4428e54]
+description = "Sequence of two protein codons translates into proteins"
+
+[dd22eef3-b4f1-4ad6-bb0b-27093c090a9d]
+description = "Sequence of two different protein codons translates into proteins"
+
 [d0f295df-fb70-425c-946c-ec2ec185388e]
 description = "Translate RNA strand into correct protein list"
 
@@ -70,3 +86,22 @@ description = "Translation stops if STOP codon in middle of three-codon sequence
 
 [2c2a2a60-401f-4a80-b977-e0715b23b93d]
 description = "Translation stops if STOP codon in middle of six-codon sequence"
+
+[f6f92714-769f-4187-9524-e353e8a41a80]
+description = "Sequence of two non-STOP codons does not translate to a STOP codon"
+
+[1e75ea2a-f907-4994-ae5c-118632a1cb0f]
+description = "Non-existing codon can't translate"
+include = false
+
+[9eac93f3-627a-4c90-8653-6d0a0595bc6f]
+description = "Unknown amino acids, not part of a codon, can't translate"
+reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f"
+include = false
+
+[9d73899f-e68e-4291-b1e2-7bf87c00f024]
+description = "Incomplete RNA sequence can't translate"
+include = false
+
+[43945cf7-9968-402d-ab9f-b8a28750b050]
+description = "Incomplete RNA sequence can translate if valid until a STOP codon"
diff --git a/exercises/practice/protein-translation/test/protein_translation_test.clj b/exercises/practice/protein-translation/test/protein_translation_test.clj
index 05981ed..2b25c7b 100644
--- a/exercises/practice/protein-translation/test/protein_translation_test.clj
+++ b/exercises/practice/protein-translation/test/protein_translation_test.clj
@@ -1,36 +1,138 @@
 (ns protein-translation-test
-  (:require [clojure.test :refer [deftest is are]]
+  (:require [clojure.test :refer [deftest testing is]]
             protein-translation))
 
-(deftest AUG-translates-to-Methionine
-  (is (= "Methionine" (protein-translation/translate-codon "AUG"))))
+(deftest empty-rna-sequence-results-in-no-protein
+  (testing "Empty RNA sequence results in no protein"
+    (is (= [] (protein-translation/translate-rna "")))))
 
-(deftest UGG-translates-to-Tryptophan
-  (is (= "Tryptophan" (protein-translation/translate-codon "UGG"))))
+(deftest methionine-RNA-sequence
+  (testing "Methionine RNA sequence"
+    (is (= ["Methionine"]
+           (protein-translation/translate-rna "AUG")))))
 
-(deftest identifies-Phenylalanine-codons
-  (are [codon] (= "Phenylalanine" (protein-translation/translate-codon codon)) "UUU" "UUC"))
+(deftest phenylalanine-RNA-sequence-1
+  (testing "Phenylalanine RNA sequence 1"
+    (is (= ["Phenylalanine"]
+           (protein-translation/translate-rna "UUU")))))
 
-(deftest identifies-Leucine-codons
-  (are [codon] (= "Leucine" (protein-translation/translate-codon codon)) "UUA" "UUG"))
+(deftest phenylalanine-RNA-sequence-2
+  (testing "Phenylalanine RNA sequence 2"
+    (is (= ["Phenylalanine"]
+           (protein-translation/translate-rna "UUC")))))
 
-(deftest identiefies-Serine-codons
-  (are [codon] (= "Serine" (protein-translation/translate-codon codon)) "UCU" "UCC" "UCA" "UCG"))
+(deftest leucine-RNA-sequence-1
+  (testing "Leucine RNA sequence 1"
+    (is (= ["Leucine"]
+           (protein-translation/translate-rna "UUA")))))
 
-(deftest identiefies-Tyrosine-codons
-  (are [codon] (= "Tyrosine" (protein-translation/translate-codon codon)) "UAU" "UAC"))
+(deftest leucine-RNA-sequence-2
+  (testing "Leucine RNA sequence 2"
+    (is (= ["Leucine"]
+           (protein-translation/translate-rna "UUG")))))
 
-(deftest identifies-Cysteine-codons
-  (are [codon] (= "Cysteine" (protein-translation/translate-codon codon)) "UGU" "UGC"))
+(deftest serine-RNA-sequence-1
+  (testing "Serine RNA sequence 1"
+    (is (= ["Serine"]
+           (protein-translation/translate-rna "UCU")))))
 
-(deftest identifies-stop-codons
-  (are [codon] (= "STOP" (protein-translation/translate-codon codon)) "UAA" "UAG" "UGA"))
+(deftest serine-RNA-sequence-2
+  (testing "Serine RNA sequence 2"
+    (is (= ["Serine"]
+           (protein-translation/translate-rna "UCC")))))
 
-(deftest translates-rna-strand-into-correct-protein
-  (is (= ["Methionine" "Phenylalanine" "Tryptophan"] (protein-translation/translate-rna "AUGUUUUGG"))))
+(deftest serine-RNA-sequence-3
+  (testing "Serine RNA sequence 3"
+    (is (= ["Serine"]
+           (protein-translation/translate-rna "UCA")))))
 
-(deftest stops-translation-if-stop-codon-present
-  (is (= ["Methionine" "Phenylalanine"] (protein-translation/translate-rna "AUGUUUUAA"))))
+(deftest serine-RNA-sequence-4
+  (testing "Serine RNA sequence 4"
+    (is (= ["Serine"]
+           (protein-translation/translate-rna "UCG")))))
 
-(deftest stops-translation-of-longer-strand
-  (is (= ["Tryptophan" "Cysteine" "Tyrosine"] (protein-translation/translate-rna "UGGUGUUAUUAAUGGUUU"))))
+(deftest tyrosine-RNA-sequence-1
+  (testing "Tyrosine RNA sequence 1"
+    (is (= ["Tyrosine"]
+           (protein-translation/translate-rna "UAU")))))
+
+(deftest tyrosine-RNA-sequence-2
+  (testing "Tyrosine RNA sequence 2"
+    (is (= ["Tyrosine"]
+           (protein-translation/translate-rna "UAC")))))
+
+(deftest cysteine-RNA-sequence-1
+  (testing "Cysteine RNA sequence 1"
+    (is (= ["Cysteine"]
+           (protein-translation/translate-rna "UGU")))))
+
+(deftest cysteine-RNA-sequence-2
+  (testing "Cysteine RNA sequence 2"
+    (is (= ["Cysteine"]
+           (protein-translation/translate-rna "UGC")))))
+
+(deftest tryptophan-RNA-sequence
+  (testing "Tryptophan RNA sequence"
+    (is (= ["Tryptophan"]
+           (protein-translation/translate-rna "UGG")))))
+
+(deftest STOP-codon-RNA-sequence-1
+  (testing "STOP codon RNA sequence 1"
+    (is (= [] (protein-translation/translate-rna "UAA")))))
+
+(deftest STOP-codon-RNA-sequence-2
+  (testing "STOP codon RNA sequence 2"
+    (is (= [] (protein-translation/translate-rna "UAG")))))
+
+(deftest STOP-codon-RNA-sequence-3
+  (testing "STOP codon RNA sequence 3"
+    (is (= [] (protein-translation/translate-rna "UGA")))))
+
+(deftest sequence-of-two-protein-codons-translates-into-proteins
+  (testing "Sequence of two protein codons translates into proteins"
+    (is (= ["Phenylalanine", "Phenylalanine"]
+           (protein-translation/translate-rna "UUUUUU")))))
+
+(deftest sequence-of-two-different-protein-codons-translates-into-proteins
+  (testing "Sequence of two different protein codons translates into proteins"
+    (is (= ["Leucine", "Leucine"]
+           (protein-translation/translate-rna "UUAUUG")))))
+
+(deftest translate-RNA-strand-into-correct-protein-list
+  (testing "Translate RNA strand into correct protein list"
+    (is (= ["Methionine", "Phenylalanine", "Tryptophan"]
+           (protein-translation/translate-rna "AUGUUUUGG")))))
+
+(deftest translation-stops-if-STOP-codon-at-beginning-of-sequence
+  (testing "Translation stops if STOP codon at beginning of sequence"
+    (is (= [] (protein-translation/translate-rna "UAGUGG")))))
+
+(deftest translation-stops-if-STOP-codon-at-end-of-two-codon-sequence
+  (testing "Translation stops if STOP codon at end of two-codon sequence"
+    (is (= ["Tryptophan"]
+           (protein-translation/translate-rna "UGGUAG")))))
+
+(deftest translation-stops-if-STOP-codon-at-end-of-three-codon-sequence
+  (testing "Translation stops if STOP codon at end of three-codon sequence"
+    (is (= ["Methionine", "Phenylalanine"]
+           (protein-translation/translate-rna "AUGUUUUAA")))))
+
+(deftest translation-stops-if-STOP-codon-in-middle-of-three-codon-sequence
+  (testing "Translation stops if STOP codon in middle of three-codon sequence"
+    (is (= ["Tryptophan"]
+           (protein-translation/translate-rna "UGGUAGUGG")))))
+
+(deftest translation-stops-if-STOP-codon-in-middle-of-six-codon-sequence
+  (testing "Translation stops if STOP codon in middle of six-codon sequence"
+    (is (= ["Tryptophan", "Cysteine", "Tyrosine"]
+           (protein-translation/translate-rna "UGGUGUUAUUAAUGGUUU")))))
+
+(deftest sequence-of-two-non-STOP-codons-does-not-translate-to-a-STOP-codon
+  (testing "Sequence of two non-STOP codons does not translate to a STOP codon"
+    (is (= ["Methionine", "Methionine"]
+           (protein-translation/translate-rna "AUGAUG")))))
+
+(deftest incomplete-RNA-sequence-can-translate-if-valid-until-a-STOP-codon
+  (testing "Incomplete RNA sequence can translate if valid until a STOP codon"
+    (is (= ["Phenylalanine", "Phenylalanine"]
+           (protein-translation/translate-rna "UUCUUCUAAUGGU")))))
diff --git a/exercises/practice/scrabble-score/test/scrabble_score_test.clj b/exercises/practice/scrabble-score/test/scrabble_score_test.clj
index 1fbd097..09a2f40 100644
--- a/exercises/practice/scrabble-score/test/scrabble_score_test.clj
+++ b/exercises/practice/scrabble-score/test/scrabble_score_test.clj
@@ -1,21 +1,47 @@
 (ns scrabble-score-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest testing is]]
             scrabble-score))
 
-(deftest lower-case-letter
-  (is (= 1 (scrabble-score/score-letter "a"))))
+(deftest lowercase-letter
+  (testing "Lowercase letter"
+    (is (= 1 (scrabble-score/score-word "a")))))
 
-(deftest upper-case-letter
-  (is (= 1 (scrabble-score/score-letter "A"))))
+(deftest uppercase-letter
+  (testing "Uppercase letter"
+    (is (= 1 (scrabble-score/score-word "A")))))
 
-(deftest two-letter-word
-  (is (= 2 (scrabble-score/score-word "at"))))
+(deftest valuable-letter
+  (testing "Valuable letter"
+    (is (= 4 (scrabble-score/score-word "f")))))
 
-(deftest bigger-word-1
-  (is (= 6 (scrabble-score/score-word "street"))))
+(deftest short-word
+  (testing "Short word"
+    (is (= 2 (scrabble-score/score-word "at")))))
 
-(deftest bigger-word-2
-  (is (= 22 (scrabble-score/score-word "quirky"))))
+(deftest short-valuable-word
+  (testing "Short, valuable word"
+    (is (= 12 (scrabble-score/score-word "zoo")))))
 
-(deftest all-upper-case-word
-  (is (= 41 (scrabble-score/score-word "OXYPHENBUTAZONE"))))
+(deftest medium-word
+  (testing "Medium word"
+    (is (= 6 (scrabble-score/score-word "street")))))
+
+(deftest medium-valuable-word
+  (testing "medium, valuable word"
+    (is (= 22 (scrabble-score/score-word "quirky")))))
+
+(deftest long-mixed-case-word
+  (testing "Long, mixed-case word"
+    (is (= 41 (scrabble-score/score-word "OxyphenButazone")))))
+
+(deftest english-like-word
+  (testing "English-like word"
+    (is (= 8 (scrabble-score/score-word "pinata")))))
+
+(deftest empty-input
+  (testing "Empty input"
+    (is (= 0 (scrabble-score/score-word "")))))
+
+(deftest entire-alphabet-available
+  (testing "Entire alphabet available"
+    (is (= 87 (scrabble-score/score-word "abcdefghijklmnopqrstuvwxyz")))))
diff --git a/exercises/practice/space-age/.meta/tests.toml b/exercises/practice/space-age/.meta/tests.toml
index b4a221d..5e25a02 100644
--- a/exercises/practice/space-age/.meta/tests.toml
+++ b/exercises/practice/space-age/.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.
 
 [84f609af-5a91-4d68-90a3-9e32d8a5cd34]
 description = "age on Earth"
@@ -25,3 +32,8 @@ description = "age on Uranus"
 
 [80096d30-a0d4-4449-903e-a381178355d8]
 description = "age on Neptune"
+
+[57b96e2a-1178-40b7-b34d-f3c9c34e4bf4]
+description = "invalid planet causes error"
+include = false
+comment = "excluded because the existing tests use a separate function for each planet"