Repository: exercism/clojure
Pull request #716 (2025-01-05 11:15:14)

Add script to scaffold new practice exercises

ErikSchierboom: I've also updated the PR to write the namespaces to the solution, example and test files.

Patch:
diff --git a/bin/add-practice-exercise b/bin/add-practice-exercise
new file mode 100755
index 0000000..f27001d
--- /dev/null
+++ b/bin/add-practice-exercise
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+
+# Synopsis:
+# Scaffold the files for a new practice exercise.
+# After creating the exercise, follow the instructions in the output.
+
+# Example:
+# bin/add-practice-exercise two-fer
+
+# Example with difficulty:
+# bin/add-practice-exercise -d 5 two-fer
+
+# Example with author and difficulty:
+# bin/add-practice-exercise -a foo -d 3 two-fer
+
+set -euo pipefail
+scriptname=$0
+
+help_and_exit() {
+    echo >&2 "Scaffold the files for a new practice exercise."
+    echo >&2 "Usage: ${scriptname} [-h] [-a author] [-d difficulty] <exercise-slug>"
+    echo >&2 "Where: author is the GitHub username of the exercise creator."
+    echo >&2 "Where: difficulty is between 1 (easiest) to 10 (hardest)."
+    exit 1
+}
+
+die() { echo >&2 "$*"; exit 1; }
+
+required_tool() {
+    command -v "${1}" >/dev/null 2>&1 ||
+        die "${1} is required but not installed. Please install it and make sure it's in your PATH."
+}
+
+require_files_template() {
+    jq -e --arg key "${1}" '.files[$key] | length > 0' config.json > /dev/null ||
+        die "The '.files.${1}' array in the 'config.json' file is empty. Please add at least one file. See https://exercism.org/docs/building/tracks/config-json#h-files for more information."
+}
+
+required_tool jq
+
+require_files_template "solution"
+require_files_template "test"
+require_files_template "example"
+
+[[ -f ./bin/fetch-configlet ]] || die "Run this script from the repo's root directory."
+
+author=''
+difficulty='1'
+while getopts :ha:d: opt; do
+    case $opt in
+        h) help_and_exit ;;
+        a) author=$OPTARG ;;
+        d) difficulty=$OPTARG ;;
+        ?) echo >&2 "Unknown option: -$OPTARG"; help_and_exit ;;
+    esac
+done
+shift "$((OPTIND - 1))"
+
+(( $# >= 1 )) || help_and_exit
+
+slug="${1}"
+
+if [[ -z "${author}" ]]; then
+    read -rp 'Your GitHub username: ' author
+fi
+
+./bin/fetch-configlet
+./bin/configlet create --practice-exercise "${slug}" --author "${author}" --difficulty "${difficulty}"
+
+exercise_dir="exercises/practice/${slug}"
+config_json_file="${exercise_dir}/.meta/config.json"
+files=$(jq -r --arg dir "${exercise_dir}" '.files | to_entries | map({key: .key, value: (.value | map("'"'"'" + $dir + "/" + . + "'"'"'") | join(" and "))}) | from_entries' "${config_json_file}")
+
+sample_exercise_dir="exercises/practice/acronym"
+for sample_file in deps.edn project.clj; do
+    if [[ ! -f "${sample_exercise_dir}/${sample_file}" ]]; then
+        die "The sample exercise directory is missing the '${sample_file}' file."
+    fi
+
+    cp "${sample_exercise_dir}/${sample_file}" "${exercise_dir}/${sample_file}"
+done
+
+sed -i "s/acronym/${slug}/g" "${exercise_dir}/project.clj"
+
+test_file=$(jq -r '.files.test[0]' "${config_json_file}")
+cat >"${exercise_dir}/${test_file}" << TEST_FILE
+(ns ${slug}-test
+  (:require [clojure.test :refer [deftest testing is]]
+            ${slug}))
+TEST_FILE
+
+for file_type in solution example; do
+    solution_file=$(jq -r --arg file_type "${file_type}" '.files[$file_type][0]' "${config_json_file}")
+    cat >"${exercise_dir}/${solution_file}" << FILE
+(ns ${slug})
+FILE
+done
+
+cat << NEXT_STEPS
+Your next steps are:
+- Create the test suite in $(jq -r '.test' <<< "${files}")
+  - The tests should be based on the canonical data at 'https://github.com/exercism/problem-specifications/blob/main/exercises/${slug}/canonical-data.json'
+  - Any test cases you don't implement, mark them in 'exercises/practice/${slug}/.meta/tests.toml' with "include = false"
+- Create the example solution in $(jq -r '.example' <<< "${files}")
+- Verify the example solution passes the tests by running 'bin/verify-exercises ${slug}'
+- Create the stub solution in $(jq -r '.solution' <<< "${files}")
+- Update the 'difficulty' value for the exercise's entry in the 'config.json' file in the repo's root
+- Validate CI using 'bin/configlet lint' and 'bin/configlet fmt'
+NEXT_STEPS
diff --git a/concepts/lists/.meta/config.json b/concepts/lists/.meta/config.json
index bb8ee53..4e4843a 100644
--- a/concepts/lists/.meta/config.json
+++ b/concepts/lists/.meta/config.json
@@ -1,4 +1,11 @@
 {
   "blurb": "Lists are immutable, sequential collections representing function calls unless quoted.",
-  "authors": ["porkostomus", "bemself", "cstby"]
+  "authors": [
+    "porkostomus",
+    "bemself",
+    "cstby"
+  ],
+  "contributors": [
+    "tasxatzial"
+  ]
 }
diff --git a/concepts/lists/about.md b/concepts/lists/about.md
index b7687f4..b88e95e 100644
--- a/concepts/lists/about.md
+++ b/concepts/lists/about.md
@@ -3,17 +3,16 @@
 ## Key Learnings
 
 - Lists are collections.
-- Lists can be created using `list` or by using a single quote.
+- Lists can be created using [`list`][list] or by using a single quote.
 - Clojure will try to evaluate lists, treating the first item as a function.
 - Core functions:
-  - `cons` returns a list with the new item added to beginning.
-  - `first` returns the first item from a list.
-  - `rest` returns the list without the first item.
-  - `count` returns the number of items in the list.
-  - `conj` returns a list with items appended in it.
+  - [`first`][first] returns the first item in the list.
+  - [`rest`][rest] returns a list of all items of the list except the first.
+  - [`count`][count] returns the number of items in the list.
+  - [`conj`][conj] adds one or more items to the beginning of the list.
 
-## Additional Resources
-
-- [list - clojure.core | ClojureDocs](https://clojuredocs.org/clojure.core/list)
-- [Data Structures](https://clojure.org/reference/data_structures)
-- [Lists in Clojurescript](https://cljs.github.io/api/syntax/list)
+[list]: https://clojuredocs.org/clojure.core/list
+[first]: https://clojuredocs.org/clojure.core/first
+[rest]: https://clojuredocs.org/clojure.core/rest
+[count]: https://clojuredocs.org/clojure.core/count
+[conj]: https://clojuredocs.org/clojure.core/conj
diff --git a/concepts/lists/links.json b/concepts/lists/links.json
index 36bbabe..2333e7b 100644
--- a/concepts/lists/links.json
+++ b/concepts/lists/links.json
@@ -1,14 +1,6 @@
 [
-  {
-    "url": "https://clojuredocs.org/clojure.core/list",
-    "description": "list - clojure.core | ClojureDocs"
-  },
   {
     "url": "https://clojure.org/reference/data_structures",
     "description": "Data Structures"
-  },
-  {
-    "url": "https://cljs.github.io/api/syntax/list",
-    "description": "Lists in Clojurescript"
   }
 ]
diff --git a/config.json b/config.json
index a2a1415..f1b2aba 100644
--- a/config.json
+++ b/config.json
@@ -1195,6 +1195,17 @@
           "math"
         ]
       },
+      {
+        "slug": "pythagorean-triplet",
+        "name": "Pythagorean Triplet",
+        "uuid": "7a9e219b-a830-4813-8274-e4473fef29e8",
+        "practices": [],
+        "prerequisites": [
+          "basics",
+          "numbers"
+        ],
+        "difficulty": 6
+      },
       {
         "slug": "robot-simulator",
         "name": "Robot Simulator",
diff --git a/exercises/concept/annalyns-infiltration/.docs/hints.md b/exercises/concept/annalyns-infiltration/.docs/hints.md
index 9d417d4..df4899e 100644
--- a/exercises/concept/annalyns-infiltration/.docs/hints.md
+++ b/exercises/concept/annalyns-infiltration/.docs/hints.md
@@ -2,19 +2,19 @@
 
 ## 1. Check if a fast attack can be made
 
-We need to check if the knight is awake and _invert_ its truth value. This can be done with the [`not`][not] function.
+We need to check if the knight is awake and _invert_ the boolean value representing their state. This can be done with the [`not`][not] function.
 
 ## 2. Check if the group can be spied upon
 
-We want the function to return `true` if _any_ of the supplied predicates are `true`. This can be done using the [`or`][or] function.
+We want to return `true` if _any_ of the supplied predicates are `true`. This can be done with the [`or`][or] function.
 
-## 3. Check if the prisoner can be signalled
+## 3. Check if the prisoner can be signaled
 
 We want to return `true` if and only if _all_ of the supplied predicates are `true`. This can be done with the [`and`][and] function.
 
 ## 4. Check if the prisoner can be freed
 
-Here, we need to combine the three basic logical operators, [`and`][and], [`or`][or], and [`not`][not].
+We need to combine the three basic logical operators, [`and`][and], [`or`][or], and [`not`][not].
 
 [not]: https://clojuredocs.org/clojure.core/not
 [or]: https://clojuredocs.org/clojure.core/or
diff --git a/exercises/concept/annalyns-infiltration/.docs/instructions.md b/exercises/concept/annalyns-infiltration/.docs/instructions.md
index b5b7976..69563bf 100644
--- a/exercises/concept/annalyns-infiltration/.docs/instructions.md
+++ b/exercises/concept/annalyns-infiltration/.docs/instructions.md
@@ -1,26 +1,35 @@
 # Instructions
 
-In this exercise, you'll be implementing the quest logic for a new RPG game a friend is developing. The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog. Unfortunately, disaster strikes, as her best friend was kidnapped while searching for berries in the forest. Annalyn will try to find and free her best friend, optionally taking her dog with her on this quest.
+In this exercise, you'll implement the quest logic for a new RPG game that a friend is developing.
+The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog.
+Unfortunately, disaster strikes: her best friend was kidnapped while searching for berries in the forest.
+Annalyn will try to find and rescue her friend, optionally taking her dog along on the quest.
 
-After some time spent following her best friend's trail, she finds the camp in which her best friend is imprisoned. It turns out there are two kidnappers: a mighty knight and a cunning archer.
+After some time spent following the trail, Annalyn discovers the camp where her friend is imprisoned.
+It turns out there are two kidnappers: a mighty knight and a cunning archer.
 
 Having found the kidnappers, Annalyn considers which of the following actions she can engage in:
 
-- Fast attack: a fast attack can be made if the knight is sleeping, as it takes time for him to get his armor on, so he will be vulnerable.
-- Spy: the group can be spied upon if at least one of them is awake. Otherwise, spying is a waste of time.
-- Signal prisoner: the prisoner can be signalled using bird sounds if the prisoner is awake and the archer is sleeping, as archers are trained in bird signaling so they could intercept the message.
-- _Free prisoner_: Annalyn can try sneaking into the camp to free the prisoner.
-  This is a risky thing to do and can only succeed in one of two ways:
-  - If Annalyn has her pet dog with her she can rescue the prisoner if the archer is asleep.
+- Fast attack: a fast attack can be made if the knight is sleeping, as it takes time for him to put on his armor, leaving him vulnerable.
+- Spy: the group can be spied upon if at least one of them is awake.
+  Otherwise, spying is a waste of time.
+- Signal prisoner: the prisoner can be signaled using bird sounds if the prisoner is awake and the archer is sleeping.
+  Archers are trained in bird signaling and could intercept the message if they are awake.
+- _Free prisoner_: Annalyn can attempt to sneak into the camp to free the prisoner.
+  This is risky and can only succeed in one of two ways:
+  - If Annalyn has her pet dog, she can rescue the prisoner if the archer is asleep.
     The knight is scared of the dog and the archer will not have time to get ready before Annalyn and the prisoner can escape.
-  - If Annalyn does not have her dog then she and the prisoner must be very sneaky!
-    Annalyn can free the prisoner if the prisoner is awake and the knight and archer are both sleeping, but if the prisoner is sleeping they can't be rescued: the prisoner would be startled by Annalyn's sudden appearance and wake up the knight and archer.
+  - If Annalyn does not have her pet dog, then she and the prisoner must be very sneaky!
+    Annalyn can free the prisoner if the prisoner is awake and both the knight and archer are sleeping.
+    However, if the prisoner is sleeping, they can't be rescued, as the prisoner would be startled by Annalyn's sudden appearance and wake up the knight and archer.
 
-You have four tasks: to implement the logic for determining if the above actions are available based on the state of the three characters found in the forest and whether Annalyn's pet dog is present or not.
+You have four tasks: to implement the logic for determining if the above actions are available based on the state of the three characters in the forest and whether Annalyn's pet dog is present or not.
 
 ## 1. Check if a fast attack can be made
 
-Implement the `can-fast-attack?` function, which takes a boolean value indicating whether the knight is awake. The function returns `true` if a fast attack can be made based on the state of the knight. Otherwise, it returns `false`:
+Implement the `can-fast-attack?` function, which takes a boolean value indicating whether the knight is awake.
+The function returns `true` if a fast attack can be made based on the state of the knight.
+Otherwise, it returns `false`:
 
 ```clojure
 (def knight-awake? true)
@@ -31,7 +40,9 @@ Implement the `can-fast-attack?` function, which takes a boolean value indicatin
 
 ## 2. Check if the group can be spied upon
 
-Implement the `can-spy?` function, which takes three boolean values indicating whether the knight, archer and the prisoner, respectively, are awake. The function returns `true` if the group can be spied upon based on the state of the three characters. Otherwise, it returns `false`:
+Implement the `can-spy?` function, which takes three boolean values indicating whether the knight, archer, and prisoner, respectively, are awake.
+The function returns `true` if the group can be spied upon based on the state of the three characters.
+Otherwise, it returns `false`:
 
 ```clojure
 (def knight-awake? false)
@@ -42,9 +53,11 @@ Implement the `can-spy?` function, which takes three boolean values indicating w
 ;;=> true
 ```
 
-## 3. Check if the prisoner can be signalled
+## 3. Check if the prisoner can be signaled
 
-Implement the `can-signal-prisoner?` function, which takes two boolean values indicating whether the archer and the prisoner, respectively, are awake. The function returns `true` if the prisoner can be signalled based on the state of the two characters. Otherwise, it returns `false`:
+Implement the `can-signal-prisoner?` function, which takes two boolean values indicating whether the archer and the prisoner, respectively, are awake.
+The function returns `true` if the prisoner can be signaled based on the state of the two characters.
+Otherwise, it returns `false`:
 
 ```clojure
 (def archer-awake? false)
@@ -56,7 +69,11 @@ Implement the `can-signal-prisoner?` function, which takes two boolean values in
 
 ## 4. Check if the prisoner can be freed
 
-Implement the `can-free-prisoner?` function, which takes four boolean values. The first three parameters indicate whether the knight, archer and the prisoner, respectively, are awake. The last parameter indicates whether Annalyn's pet dog is present. The function returns `true` if the prisoner can be freed based on the state of the three characters and Annalyn's pet dog presence. Otherwise, it returns `false`:
+Implement the `can-free-prisoner?` function, which takes four boolean values.
+The first three parameters indicate whether the knight, archer, and prisoner, respectively, are awake.
+The last parameter indicates whether Annalyn's pet dog is present.
+The function returns `true` if the prisoner can be freed based on the state of the three characters and the presence of Annalyn's pet dog.
+Otherwise, it returns `false`:
 
 ```clojure
 (def knight-awake? false)
diff --git a/exercises/concept/annalyns-infiltration/.meta/config.json b/exercises/concept/annalyns-infiltration/.meta/config.json
index 6c4fb14..1b57b73 100644
--- a/exercises/concept/annalyns-infiltration/.meta/config.json
+++ b/exercises/concept/annalyns-infiltration/.meta/config.json
@@ -2,6 +2,9 @@
   "authors": [
     "porkostomus"
   ],
+  "contributors": [
+    "tasxatzial"
+  ],
   "files": {
     "solution": [
       "src/annalyns_infiltration.clj"
diff --git a/exercises/concept/bird-watcher/.meta/config.json b/exercises/concept/bird-watcher/.meta/config.json
index e8ae35a..154bf6d 100644
--- a/exercises/concept/bird-watcher/.meta/config.json
+++ b/exercises/concept/bird-watcher/.meta/config.json
@@ -2,6 +2,9 @@
   "authors": [
     "porkostomus"
   ],
+  "contributors": [
+    "tasxatzial"
+  ],
   "files": {
     "solution": [
       "src/bird_watcher.clj"
diff --git a/exercises/concept/cars-assemble/.meta/config.json b/exercises/concept/cars-assemble/.meta/config.json
index 67a05aa..30a49f5 100644
--- a/exercises/concept/cars-assemble/.meta/config.json
+++ b/exercises/concept/cars-assemble/.meta/config.json
@@ -2,6 +2,9 @@
   "authors": [
     "porkostomus"
   ],
+  "contributors": [
+    "tasxatzial"
+  ],
   "files": {
     "solution": [
       "src/cars_assemble.clj"
diff --git a/exercises/concept/interest-is-interesting/.meta/config.json b/exercises/concept/interest-is-interesting/.meta/config.json
index fcb51b6..7d6b8db 100644
--- a/exercises/concept/interest-is-interesting/.meta/config.json
+++ b/exercises/concept/interest-is-interesting/.meta/config.json
@@ -2,6 +2,9 @@
   "authors": [
     "porkostomus"
   ],
+  "contributors": [
+    "tasxatzial"
+  ],
   "files": {
     "solution": [
       "src/interest_is_interesting.clj"
diff --git a/exercises/concept/squeaky-clean/.meta/config.json b/exercises/concept/squeaky-clean/.meta/config.json
index 83420b6..7f397ec 100644
--- a/exercises/concept/squeaky-clean/.meta/config.json
+++ b/exercises/concept/squeaky-clean/.meta/config.json
@@ -2,6 +2,9 @@
   "authors": [
     "porkostomus"
   ],
+  "contributors": [
+    "tasxatzial"
+  ],
   "files": {
     "solution": [
       "src/squeaky_clean.clj"
diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json
index d6655ac..a39c443 100644
--- a/exercises/practice/armstrong-numbers/.meta/config.json
+++ b/exercises/practice/armstrong-numbers/.meta/config.json
@@ -7,7 +7,8 @@
     "imtayadeway",
     "mawis",
     "mtkp",
-    "sjwarner-bp"
+    "sjwarner-bp",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/armstrong-numbers/.meta/src/example.clj b/exercises/practice/armstrong-numbers/.meta/src/example.clj
index 3900b6e..db4ead2 100644
--- a/exercises/practice/armstrong-numbers/.meta/src/example.clj
+++ b/exercises/practice/armstrong-numbers/.meta/src/example.clj
@@ -1,7 +1,7 @@
 (ns armstrong-numbers)
 
 (defn expt [base pow]
-  (reduce * 1 (repeat pow base)))
+  (reduce *' 1 (repeat pow base)))
 
 (defn armstrong? [n]
   (let [digits (map (comp read-string str) (str n))
diff --git a/exercises/practice/armstrong-numbers/src/armstrong_numbers.clj b/exercises/practice/armstrong-numbers/src/armstrong_numbers.clj
index da6002b..3d4e44a 100644
--- a/exercises/practice/armstrong-numbers/src/armstrong_numbers.clj
+++ b/exercises/practice/armstrong-numbers/src/armstrong_numbers.clj
@@ -1,5 +1,7 @@
 (ns armstrong-numbers)
 
-(defn armstrong? [num] ;; <- arglist goes here
-  ;; your code goes here
-)
+(defn armstrong?
+  "Returns true if the given number is an Armstrong number; otherwise, returns false"
+  [num]
+  ;; function body
+  )
diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json
index e2fe40d..3f2f53b 100644
--- a/exercises/practice/binary-search/.meta/config.json
+++ b/exercises/practice/binary-search/.meta/config.json
@@ -9,7 +9,8 @@
     "kytrinyx",
     "querenker",
     "ryanplusplus",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/complex-numbers/.docs/instructions.append.md b/exercises/practice/complex-numbers/.docs/instructions.append.md
index 1c6bfad..0d6dbb2 100644
--- a/exercises/practice/complex-numbers/.docs/instructions.append.md
+++ b/exercises/practice/complex-numbers/.docs/instructions.append.md
@@ -1,4 +1,10 @@
 # Instructions append
 
-**Note:** The instructions ask for the exponent function because they are synced with a shared repository to maintain consistency across all language tracks.
-However, for this exercise in the Clojure track, you don't need to implement it.
+## Appendix
+
+~~~~exercism/note
+The instructions above are synchronized with a shared repository to ensure consistency across all language tracks.
+This appendix provides additional clarification or modifies the instructions as needed to better align with the goals of the Clojure track.
+~~~~
+
+For this exercise in the Clojure track, you don't need to implement the exponentiation operation.
diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md
index 50b19ae..2b8a7a4 100644
--- a/exercises/practice/complex-numbers/.docs/instructions.md
+++ b/exercises/practice/complex-numbers/.docs/instructions.md
@@ -1,29 +1,100 @@
 # Instructions
 
-A complex number is a number in the form `a + b * i` where `a` and `b` are real and `i` satisfies `i^2 = -1`.
+A **complex number** is expressed in the form `z = a + b * i`, where:
 
-`a` is called the real part and `b` is called the imaginary part of `z`.
-The conjugate of the number `a + b * i` is the number `a - b * i`.
-The absolute value of a complex number `z = a + b * i` is a real number `|z| = sqrt(a^2 + b^2)`. The square of the absolute value `|z|^2` is the result of multiplication of `z` by its complex conjugate.
+- `a` is the **real part** (a real number),
 
-The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately:
-`(a + i * b) + (c + i * d) = (a + c) + (b + d) * i`,
-`(a + i * b) - (c + i * d) = (a - c) + (b - d) * i`.
+- `b` is the **imaginary part** (also a real number), and
 
-Multiplication result is by definition
-`(a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i`.
+- `i` is the **imaginary unit** satisfying `i^2 = -1`.
 
-The reciprocal of a non-zero complex number is
-`1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i`.
+## Operations on Complex Numbers
 
-Dividing a complex number `a + i * b` by another `c + i * d` gives:
-`(a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i`.
+### Conjugate
 
-Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`.
+The conjugate of the complex number `z = a + b * i` is given by:
 
-Implement the following operations:
+```text
+zc = a - b * i
+```
 
-- addition, subtraction, multiplication and division of two complex numbers,
-- conjugate, absolute value, exponent of a given complex number.
+### Absolute Value
 
-Assume the programming language you are using does not have an implementation of complex numbers.
+The absolute value (or modulus) of `z` is defined as:
+
+```text
+|z| = sqrt(a^2 + b^2)
+```
+
+The square of the absolute value is computed as the product of `z` and its conjugate `zc`:
+
+```text
+|z|^2 = z * zc = a^2 + b^2
+```
+
+### Addition
+
+The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately:
+
+```text
+z1 + z2 = (a + b * i) + (c + d * i)
+        = (a + c) + (b + d) * i
+```
+
+### Subtraction
+
+The difference of two complex numbers is obtained by subtracting their respective parts:
+
+```text
+z1 - z2 = (a + b * i) - (c + d * i)
+        = (a - c) + (b - d) * i
+```
+
+### Multiplication
+
+The product of two complex numbers is defined as:
+
+```text
+z1 * z2 = (a + b * i) * (c + d * i)
+        = (a * c - b * d) + (b * c + a * d) * i
+```
+
+### Reciprocal
+
+The reciprocal of a non-zero complex number is given by:
+
+```text
+1 / z = 1 / (a + b * i)
+      = a / (a^2 + b^2) - b / (a^2 + b^2) * i
+```
+
+### Division
+
+The division of one complex number by another is given by:
+
+```text
+z1 / z2 = z1 * (1 / z2)
+        = (a + b * i) / (c + d * i)
+        = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i
+```
+
+### Exponentiation
+
+Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula:
+
+```text
+e^(a + b * i) = e^a * e^(b * i)
+              = e^a * (cos(b) + i * sin(b))
+```
+
+## Implementation Requirements
+
+Given that you should not use built-in support for complex numbers, implement the following operations:
+
+- **addition** of two complex numbers
+- **subtraction** of two complex numbers
+- **multiplication** of two complex numbers
+- **division** of two complex numbers
+- **conjugate** of a complex number
+- **absolute value** of a complex number
+- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number
diff --git a/exercises/practice/dominoes/.meta/config.json b/exercises/practice/dominoes/.meta/config.json
index ca3f760..f2060e5 100644
--- a/exercises/practice/dominoes/.meta/config.json
+++ b/exercises/practice/dominoes/.meta/config.json
@@ -3,7 +3,8 @@
   "contributors": [
     "AndreaCrotti",
     "haus",
-    "sjwarner-bp"
+    "sjwarner-bp",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/dominoes/src/dominoes.clj b/exercises/practice/dominoes/src/dominoes.clj
index d0dceb2..315d6a4 100644
--- a/exercises/practice/dominoes/src/dominoes.clj
+++ b/exercises/practice/dominoes/src/dominoes.clj
@@ -1,5 +1,7 @@
 (ns dominoes)
 
-(defn can-chain? [] ;; <- arglist goes here
-  ;; your code goes here
-)
+(defn can-chain?
+  "Given a collection of dominoes, it returns true if they can be arranged into a chain; otherwise, it returns false"
+  [dominoes]
+  ;; function body
+  )
\ No newline at end of file
diff --git a/exercises/practice/hello-world/.docs/instructions.append.md b/exercises/practice/hello-world/.docs/instructions.append.md
index 1cea9ec..ea62e3a 100644
--- a/exercises/practice/hello-world/.docs/instructions.append.md
+++ b/exercises/practice/hello-world/.docs/instructions.append.md
@@ -2,9 +2,9 @@
 
 ## Project Structure
 
-Clojure exercises in exercism support the two most common tools for dependency management and testing, [leiningen](http://leiningen.org/) and the [Clojure CLI](https://clojure.org/guides/deps_and_cli).
+Clojure exercises in Exercism support the two most common tools for dependency management and testing, [leiningen][leiningen] and the [Clojure CLI][clojure-cli].
 
-You will find a test file named `hello_world_test.clj` inside `test` directory.
+You will find a test file named `hello_world_test.clj` inside the `test` directory.
 Write your code in `src/hello_world.clj`. It should use the namespace `hello-world` so that tests can pick it up.
 
 ### Running tests using the Clojure CLI
@@ -30,4 +30,8 @@ Then submit the exercise using:
 $ exercism submit src/hello_world.clj
 ```
 
-For more detailed instructions and learning resources refer [exercism's clojure language page](http://exercism.org/languages/clojure).
+For more detailed instructions and learning resources refer to [Exercism's Clojure language page][exercism-clojure].
+
+[leiningen]: https://leiningen.org
+[clojure-cli]: https://clojure.org/guides/deps_and_cli
+[exercism-clojure]: https://exercism.org/tracks/clojure
diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json
index cea6fa5..a018654 100644
--- a/exercises/practice/kindergarten-garden/.meta/config.json
+++ b/exercises/practice/kindergarten-garden/.meta/config.json
@@ -14,7 +14,8 @@
     "mwfogleman",
     "rubysolo",
     "sjwarner-bp",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md
index d27f6e1..7553c97 100644
--- a/exercises/practice/list-ops/.docs/instructions.append.md
+++ b/exercises/practice/list-ops/.docs/instructions.append.md
@@ -1,19 +1,27 @@
-# Instructions append
-
-## Appendix
-
-The instructions are synced with a shared repository to ensure consistency across all language tracks.
-For this exercise in the Clojure track, assume both the input and output are vectors.
-As a stretch goal, consider how you could implement the solution without using lists anywhere in your code.
-Also, think about the efficiency of your program.
-
-It is important not to reuse existing Clojure built-in functions with similar functionality, as doing so would diminish the intended learning value of the exercise.
-Key functions from the **clojure.core** namespace to avoid include `into`, `concat`, `cat`, `lazy-cat`, `mapcat`, `flatten`, `filter`, `filterv`, `remove`, `count`, `map`, `mapv`, `reduce`, `transduce`, `reverse`, and `rseq`.
-
-### Optional goals
-
-Try to pass the tests by devising a solution that assumes both the input and output are lists instead of vectors.
-The test suite does not need to be modified.
-This time, consider how you could implement the solution without using vectors anywhere in your code.
-
-If you decide to publish this solution, be sure to include a comment indicating that it addresses the optional goal of using lists. Don't forget to update the docstrings!
+# Instructions append
+
+## Appendix
+
+~~~~exercism/note
+The instructions above are synchronized with a shared repository to ensure consistency across all language tracks.
+This appendix provides additional clarification or modifies the instructions as needed to better align with the goals of the Clojure track.
+~~~~
+
+For this exercise in the Clojure track, **assume both the input and output are vectors**, as indicated by the tests.
+As a stretch goal, consider how to implement an approach that does not use lists for intermediate steps.
+
+It is important not to reuse existing Clojure built-in functions with similar functionality, as doing so would diminish the intended learning value of the exercise.
+Key functions from the `clojure.core` namespace to avoid include `into`, `concat`, `cat`, `lazy-cat`, `mapcat`, `flatten`, `filter`, `filterv`, `remove`, `count`, `map`, `mapv`, `reduce`, `transduce`, `reverse`, and `rseq`.
+
+### Optional goals
+
+The optional goal is for those who want an extra challenge and is designed with the expectation that you've already completed the main goal.
+
+Try to pass the tests by devising an approach that assumes:
+
+* Both the input and output are lists instead of vectors.
+  Note that, as usual, the tests do not need to be modified.
+* Vectors cannot be used for intermediate steps.
+
+If you decide to publish this, be sure to include a comment indicating that it addresses the optional goal of using lists.
+Don't forget to update the docstrings!
diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json
index c03a923..079a77a 100644
--- a/exercises/practice/luhn/.meta/config.json
+++ b/exercises/practice/luhn/.meta/config.json
@@ -6,7 +6,8 @@
     "AndreaCrotti",
     "haus",
     "sjwarner-bp",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/minesweeper/.meta/config.json b/exercises/practice/minesweeper/.meta/config.json
index 0ee7c9c..0c8d816 100644
--- a/exercises/practice/minesweeper/.meta/config.json
+++ b/exercises/practice/minesweeper/.meta/config.json
@@ -4,7 +4,8 @@
   ],
   "contributors": [
     "AndreaCrotti",
-    "haus"
+    "haus",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/minesweeper/src/minesweeper.clj b/exercises/practice/minesweeper/src/minesweeper.clj
index a1d9574..6fc02ce 100644
--- a/exercises/practice/minesweeper/src/minesweeper.clj
+++ b/exercises/practice/minesweeper/src/minesweeper.clj
@@ -1,5 +1,6 @@
 (ns minesweeper)
 
-(defn draw [] ;; <- arglist goes here
-  ;; your code goes here
-)
+(defn draw
+  [board]
+  ;; function body
+  )
diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json
index 8cd70f1..e0f2690 100644
--- a/exercises/practice/pascals-triangle/.meta/config.json
+++ b/exercises/practice/pascals-triangle/.meta/config.json
@@ -5,7 +5,8 @@
   "contributors": [
     "AndreaCrotti",
     "sjwarner-bp",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md
new file mode 100644
index 0000000..ced833d
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md
@@ -0,0 +1,23 @@
+# Description
+
+A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which,
+
+```text
+a² + b² = c²
+```
+
+and such that,
+
+```text
+a < b < c
+```
+
+For example,
+
+```text
+3² + 4² = 5².
+```
+
+Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`.
+
+For example, with N = 1000, there is exactly one Pythagorean triplet for which `a + b + c = 1000`: `{200, 375, 425}`.
diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md
new file mode 100644
index 0000000..3453c6e
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md
@@ -0,0 +1,19 @@
+# Introduction
+
+You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles.
+One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project.
+The letter reads:
+
+> Dear Mathematician,
+>
+> I need your help.
+> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c².
+> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N.
+> Calculating these triplets by hand would take me years, but I hear you are more than up to the task.
+>
+> Time is of the essence.
+> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem.
+
+Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition.
+Your work could have far-reaching implications, unlocking new possibilities in science and engineering.
+Can you rise to the challenge and make history?
diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json
new file mode 100644
index 0000000..3dbb467
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/.meta/config.json
@@ -0,0 +1,19 @@
+{
+  "authors": [
+    "tasxatzial"
+  ],
+  "files": {
+    "solution": [
+      "src/pythagorean_triplet.clj"
+    ],
+    "test": [
+      "test/pythagorean_triplet_test.clj"
+    ],
+    "example": [
+      ".meta/src/example.clj"
+    ]
+  },
+  "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.",
+  "source": "A variation of Problem 9 from Project Euler",
+  "source_url": "https://projecteuler.net/problem=9"
+}
diff --git a/exercises/practice/pythagorean-triplet/.meta/src/example.clj b/exercises/practice/pythagorean-triplet/.meta/src/example.clj
new file mode 100644
index 0000000..9d80fe4
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/.meta/src/example.clj
@@ -0,0 +1,11 @@
+(ns pythagorean-triplet)
+
+(defn find-pythagorean-triplets
+  [N]
+  (for [a (range N)
+        :let [b (/ (* N (- N (* 2 a))) (* 2 (- N a)))
+              c (- N a b)]
+        :when (and (pos-int? b)
+                   (< a b c)
+                   (= (* c c) (+ (* a a) (* b b))))]
+    [a b c]))
diff --git a/exercises/practice/pythagorean-triplet/deps.edn b/exercises/practice/pythagorean-triplet/deps.edn
new file mode 100644
index 0000000..561c3e2
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/deps.edn
@@ -0,0 +1,6 @@
+{:aliases {:test {:extra-paths ["test"]
+                 :extra-deps {io.github.cognitect-labs/test-runner 
+                              {:git/url "https://github.com/cognitect-labs/test-runner.git"
+                               :sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}}
+                 :main-opts ["-m" "cognitect.test-runner"]
+                 :exec-fn cognitect.test-runner.api/test}}}
\ No newline at end of file
diff --git a/exercises/practice/pythagorean-triplet/project.clj b/exercises/practice/pythagorean-triplet/project.clj
new file mode 100644
index 0000000..3fa8dff
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/project.clj
@@ -0,0 +1,4 @@
+(defproject pythagorean-triplet "0.1.0-SNAPSHOT"
+  :description "pythagorean-triplet exercise."
+  :url "https://github.com/exercism/clojure/tree/master/exercises/pythagorean-triplet"
+  :dependencies [[org.clojure/clojure "1.11.1"]])
diff --git a/exercises/practice/pythagorean-triplet/src/pythagorean_triplet.clj b/exercises/practice/pythagorean-triplet/src/pythagorean_triplet.clj
new file mode 100644
index 0000000..3966b60
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/src/pythagorean_triplet.clj
@@ -0,0 +1,7 @@
+(ns pythagorean-triplet)
+
+(defn find-pythagorean-triplets
+  "Given an integer N, it returns all Pythagorean triplets for which a + b + c = N"
+  [N]
+  ;; function body
+  )
diff --git a/exercises/practice/robot-simulator/.meta/config.json b/exercises/practice/robot-simulator/.meta/config.json
index d864a1c..21cc58a 100644
--- a/exercises/practice/robot-simulator/.meta/config.json
+++ b/exercises/practice/robot-simulator/.meta/config.json
@@ -10,7 +10,8 @@
     "haus",
     "mathias",
     "rubysolo",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json
index 2780de6..a8a87e2 100644
--- a/exercises/practice/spiral-matrix/.meta/config.json
+++ b/exercises/practice/spiral-matrix/.meta/config.json
@@ -3,7 +3,8 @@
   "contributors": [
     "AndreaCrotti",
     "haus",
-    "sjwarner-bp"
+    "sjwarner-bp",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/spiral-matrix/src/spiral_matrix.clj b/exercises/practice/spiral-matrix/src/spiral_matrix.clj
index 0020962..cd4ad00 100644
--- a/exercises/practice/spiral-matrix/src/spiral_matrix.clj
+++ b/exercises/practice/spiral-matrix/src/spiral_matrix.clj
@@ -1,5 +1,7 @@
 (ns spiral-matrix)
 
-(defn spiral [n] ;; <- arglist goes here
-  ;; your code goes here
-)
+(defn spiral
+  "Returns a spiral matrix of size n x n"
+  [n]
+  ;; function body
+  )
diff --git a/exercises/practice/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json
index 039161b..2854226 100644
--- a/exercises/practice/word-count/.meta/config.json
+++ b/exercises/practice/word-count/.meta/config.json
@@ -15,7 +15,8 @@
     "sjwarner-bp",
     "tejasbubane",
     "walkermatt",
-    "yurrriq"
+    "yurrriq",
+    "tasxatzial"
   ],
   "files": {
     "solution": [
diff --git a/exercises/practice/word-count/.meta/src/example.clj b/exercises/practice/word-count/.meta/src/example.clj
index 87c96c1..02ebb69 100644
--- a/exercises/practice/word-count/.meta/src/example.clj
+++ b/exercises/practice/word-count/.meta/src/example.clj
@@ -1,21 +1,8 @@
-(ns word-count
-  (:require [clojure.string :refer [lower-case split]]))
+(ns word-count)
 
 (defn word-count
-  "return a hash of unique words and how many times they appeared in the input string"
-  [input]
-  (->> (split input #"\W+")
-       (map lower-case)
-       (group-by identity)
-       (reduce (fn [acc [word occurrences]]
-                 (assoc acc word (count occurrences))) {})))
-
-;; Another approach
-(ns phrase
-  (:require [clojure.string :refer [lower-case]]))
-
-(defn words [s]
-  (re-seq #"\w+" s))
-
-(defn word-count [s]
-  (-> s lower-case words frequencies))
+  [phrase]
+  (->> phrase
+       clojure.string/lower-case
+       (re-seq #"\b\w+'?\w*\b")
+       frequencies))
diff --git a/exercises/practice/word-count/src/word_count.clj b/exercises/practice/word-count/src/word_count.clj
index 3d40b7d..f3f56c7 100644
--- a/exercises/practice/word-count/src/word_count.clj
+++ b/exercises/practice/word-count/src/word_count.clj
@@ -1,5 +1,7 @@
 (ns word-count)
 
-(defn word-count [s] ;; <- arglist goes here
-  ;; your code goes here
-)
+(defn word-count
+  "Counts how many times each word occurs in the given string"
+  [s]
+  ;; function body
+  )


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c3247bc..c3866db 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,26 +14,26 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: Checkout
-        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
         with:
           path: main
-          
+
       - name: Checkout test runner
-        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
         with:
           repository: exercism/clojure-test-runner
           path: clojure-test-runner
 
       - name: setup babashka
-        uses: turtlequeue/setup-babashka@2d4df498c7a578b4f3e906283f9af913590bdec7
+        uses: turtlequeue/setup-babashka@fda33428c8b62fb5bb9802ca7a2c750a6cc11b5a
         with:
           babashka-version: 0.8.1
-          
+
       - name: babashka script
         id: bb_script
         run: bb main/test.clj
-          
+
       # Print the output of the babashka script from the
-      # `bb_script` step 
+      # `bb_script` step
       - name: Get the script output
         run: echo "${{ steps.bb_script.outputs.bb_out }}"
diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml
index b956bdf..b3f09e4 100644
--- a/exercises/practice/armstrong-numbers/.meta/tests.toml
+++ b/exercises/practice/armstrong-numbers/.meta/tests.toml
@@ -38,8 +38,6 @@ description = "Seven-digit number that is not an Armstrong number"
 
 [5ee2fdf8-334e-4a46-bb8d-e5c19c02c148]
 description = "Armstrong number containing seven zeroes"
-include = false
 
 [12ffbf10-307a-434e-b4ad-c925680e1dd4]
 description = "The largest and last Armstrong number"
-include = false
diff --git a/exercises/practice/armstrong-numbers/test/armstrong_numbers_test.clj b/exercises/practice/armstrong-numbers/test/armstrong_numbers_test.clj
index 3404cf9..a2c9966 100644
--- a/exercises/practice/armstrong-numbers/test/armstrong_numbers_test.clj
+++ b/exercises/practice/armstrong-numbers/test/armstrong_numbers_test.clj
@@ -1,39 +1,47 @@
 (ns armstrong-numbers-test
   (:require [clojure.test :refer [deftest is testing]]
-            [armstrong-numbers :refer [armstrong?]]))
+            armstrong-numbers))
 
-(deftest armstrong-number-0
+(deftest test-c1ed103c-258d-45b2-be73-d8c6d9580c7b
   (testing "Zero is an Armstrong number"
-    (is (armstrong? 0))))
+    (is (true? (armstrong-numbers/armstrong? 0)))))
 
-(deftest armstrong-number-5
+(deftest test-579e8f03-9659-4b85-a1a2-d64350f6b17a
   (testing "Single-digit numbers are Armstrong numbers"
-    (is (armstrong? 5))))
+    (is (true? (armstrong-numbers/armstrong? 5)))))
 
-(deftest not-armstrong-number-10
+(deftest test-2d6db9dc-5bf8-4976-a90b-b2c2b9feba60
   (testing "There are no two-digit Armstrong numbers"
-    (is (not (armstrong? 10)))))
+    (is (false? (armstrong-numbers/armstrong? 10)))))
 
-(deftest armstrong-number-153
+(deftest test-509c087f-e327-4113-a7d2-26a4e9d18283
   (testing "Three-digit number that is an Armstrong number"
-    (is (armstrong? 153))))
+    (is (true? (armstrong-numbers/armstrong? 153)))))
 
-(deftest not-armstrong-number-100
+(deftest test-7154547d-c2ce-468d-b214-4cb953b870cf
   (testing "Three-digit number that is not an Armstrong number"
-    (is (not (armstrong? 100)))))
+    (is (false? (armstrong-numbers/armstrong? 100)))))
 
-(deftest armstrong-number-9474
+(deftest test-6bac5b7b-42e9-4ecb-a8b0-4832229aa103
   (testing "Four-digit number that is an Armstrong number"
-    (is (armstrong? 9474))))
+    (is (true? (armstrong-numbers/armstrong? 9474)))))
 
-(deftest not-armstrong-number-9475
+(deftest test-eed4b331-af80-45b5-a80b-19c9ea444b2e
   (testing "Four-digit number that is not an Armstrong number"
-    (is (not (armstrong? 9475)))))
+    (is (false? (armstrong-numbers/armstrong? 9475)))))
 
-(deftest armstrong-number-9926315
+(deftest test-f971ced7-8d68-4758-aea1-d4194900b864
   (testing "Seven-digit number that is an Armstrong number"
-    (is (armstrong? 9926315))))
+    (is (true? (armstrong-numbers/armstrong? 9926315)))))
 
-(deftest not-armstrong-number-9926314
+(deftest test-7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18
   (testing "Seven-digit number that is not an Armstrong number"
-    (is (not (armstrong? 9926314)))))
+    (is (false? (armstrong-numbers/armstrong? 9926314)))))
+
+(deftest test-5ee2fdf8-334e-4a46-bb8d-e5c19c02c148
+  (testing "Armstrong number containing seven zeroes"
+    (is (true? (armstrong-numbers/armstrong? 186709961001538790100634132976990)))))
+
+(deftest test-12ffbf10-307a-434e-b4ad-c925680e1dd4
+  (testing "The largest and last Armstrong number"
+    (is (true? (armstrong-numbers/armstrong? 115132219018763992565095597973971522401)))))
diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml
index 23ff84f..08c8e08 100644
--- a/exercises/practice/dominoes/.meta/tests.toml
+++ b/exercises/practice/dominoes/.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.
 
 [31a673f2-5e54-49fe-bd79-1c1dae476c9c]
 description = "empty input = empty output"
@@ -37,3 +44,6 @@ description = "separate loops"
 
 [cd061538-6046-45a7-ace9-6708fe8f6504]
 description = "nine elements"
+
+[44704c7c-3adb-4d98-bd30-f45527cf8b49]
+description = "separate three-domino loops"
diff --git a/exercises/practice/dominoes/test/dominoes_test.clj b/exercises/practice/dominoes/test/dominoes_test.clj
index bb1b2ef..beccaa3 100644
--- a/exercises/practice/dominoes/test/dominoes_test.clj
+++ b/exercises/practice/dominoes/test/dominoes_test.clj
@@ -1,39 +1,55 @@
 (ns dominoes-test
-  (:require [clojure.test :refer [deftest is]]
-            [dominoes     :refer [can-chain?]]))
+  (:require [clojure.test :refer [deftest testing is]]
+            dominoes))
 
-(deftest empty-input-empty-output
-  (is (can-chain? [])))
+(deftest test-31a673f2-5e54-49fe-bd79-1c1dae476c9c
+  (testing "Empty input = empty output"
+    (is (true? (dominoes/can-chain? [])))))
 
-(deftest singleton-input-singleton-output
-  (is (can-chain? [[1 1]])))
+(deftest test-4f99b933-367b-404b-8c6d-36d5923ee476
+  (testing "Singleton input = singleton output"
+    (is (true? (dominoes/can-chain? [[1 1]])))))
 
-(deftest singleton-that-cant-be-chained
-  (is ((comp not can-chain?) [[1 2]])))
+(deftest test-91122d10-5ec7-47cb-b759-033756375869
+  (testing "Singleton that can't be chained"
+    (is (false? (dominoes/can-chain? [[1 2]])))))
 
-(deftest three-elements
-  (is (can-chain?  [[1 2] [3 1] [2 3]])))
+(deftest test-be8bc26b-fd3d-440b-8e9f-d698a0623be3
+  (testing "Three elements"
+    (is (true? (dominoes/can-chain?  [[1 2] [3 1] [2 3]])))))
 
-(deftest can-reverse-dominoes
-  (is (can-chain? [[1 2] [1 3] [2 3]])))
+(deftest test-99e615c6-c059-401c-9e87-ad7af11fea5c
+  (testing "Can reverse dominoes"
+    (is (true? (dominoes/can-chain? [[1 2] [1 3] [2 3]])))))
 
-(deftest cant-be-chained
-  (is ((comp not can-chain?) [[1 2] [4 1] [2 3]])))
+(deftest test-51f0c291-5d43-40c5-b316-0429069528c9
+  (testing "Can't be chained"
+    (is (false? (dominoes/can-chain? [[1 2] [4 1] [2 3]])))))
 
-(deftest disconnected-simple
-  (is ((comp not can-chain?) [[1 1] [2 2]])))
+(deftest test-9a75e078-a025-4c23-8c3a-238553657f39
+  (testing "Disconnected - simple"
+    (is (false? (dominoes/can-chain? [[1 1] [2 2]])))))
 
-(deftest disconnected-double-loop
-  (is ((comp not can-chain?) [[1 2] [2 1] [3 4] [4 3]])))
+(deftest test-0da0c7fe-d492-445d-b9ef-1f111f07a301
+  (testing "Disconnected - double loop"
+    (is (false? (dominoes/can-chain? [[1 2] [2 1] [3 4] [4 3]])))))
 
-(deftest disconnected-single-isolated
-  (is ((comp not can-chain?) [[1 2] [2 3] [3 1] [4 4]])))
+(deftest test-b6087ff0-f555-4ea0-a71c-f9d707c5994a
+  (testing "Disconnected - single isolated"
+    (is (false? (dominoes/can-chain? [[1 2] [2 3] [3 1] [4 4]])))))
 
-(deftest need-backtrack
-  (is (can-chain? [[1 2] [2 3] [3 1] [2 4] [2 4]])))
+(deftest test-2174fbdc-8b48-4bac-9914-8090d06ef978
+  (testing "Need backtrack"
+    (is (true? (dominoes/can-chain? [[1 2] [2 3] [3 1] [2 4] [2 4]])))))
 
-(deftest separate-loops
-  (is (can-chain? [[1 2] [2 3] [3 1] [1 1] [2 2] [3 3]])))
+(deftest test-167bb480-dfd1-4318-a20d-4f90adb4a09f
+  (testing "Separate loops"
+    (is (true? (dominoes/can-chain? [[1 2] [2 3] [3 1] [1 1] [2 2] [3 3]])))))
 
-(deftest nine-elements
-  (is (can-chain? [[1 2] [5 3] [3 1] [1 2] [2 4] [1 6] [2 3] [3 4] [5 6]])))
+(deftest test-cd061538-6046-45a7-ace9-6708fe8f6504
+  (testing "Nine elements"
+    (is (true? (dominoes/can-chain? [[1 2] [5 3] [3 1] [1 2] [2 4] [1 6] [2 3] [3 4] [5 6]])))))
+
+(deftest test-44704c7c-3adb-4d98-bd30-f45527cf8b49
+  (testing "Separate three-domino loops"
+    (is (false? (dominoes/can-chain?  [[1 2] [2 3] [3 1] [4 5] [5 6] [6 4]])))))
diff --git a/exercises/practice/minesweeper/.meta/tests.toml b/exercises/practice/minesweeper/.meta/tests.toml
index d9a44cb..9b637f4 100644
--- a/exercises/practice/minesweeper/.meta/tests.toml
+++ b/exercises/practice/minesweeper/.meta/tests.toml
@@ -4,6 +4,7 @@
 
 [0c5ec4bd-dea7-4138-8651-1203e1cb9f44]
 description = "no rows"
+include = false
 
 [650ac4c0-ad6b-4b41-acde-e4ea5852c3b8]
 description = "no columns"
diff --git a/exercises/practice/minesweeper/test/minesweeper_test.clj b/exercises/practice/minesweeper/test/minesweeper_test.clj
index 1f60dcd..817763d 100644
--- a/exercises/practice/minesweeper/test/minesweeper_test.clj
+++ b/exercises/practice/minesweeper/test/minesweeper_test.clj
@@ -1,62 +1,126 @@
 (ns minesweeper-test
-  (:require [clojure.test :refer [deftest is]]
-            [clojure.string :refer [join]]
-            [minesweeper :refer [draw]]))
-
-(def line-separator (System/getProperty "line.separator"))
-
-(deftest zero-size-board
-  (is (= (draw "") "")))
-
-(deftest empty-board
-  (is (= (draw (join line-separator ["   "
-                                     "   "
-                                     "   "]))
-         (join line-separator ["   "
-                               "   "
-                               "   "]))))
-
-(deftest surrounded
-  (is (= (draw (join line-separator ["***"
-                                     "* *"
-                                     "***"]))
-         (join line-separator ["***"
-                               "*8*"
-                               "***"]))))
-
-(deftest board-full-of-mines
-  (is (= (draw (join line-separator ["***"
-                                     "***"
-                                     "***"]))
-         (join line-separator ["***"
-                               "***"
-                               "***"]))))
-
-(deftest horizontal-line
-  (is (= (draw " * * ")
-         "1*2*1")))
-
-(deftest vertical-line
-  (is (= (draw (join line-separator [" "
-                                     "*"
-                                     " "
-                                     "*"
-                                     " "]))
-         (join line-separator ["1"
-                               "*"
-                               "2"
-                               "*"
-                               "1"]))))
-
-(deftest cross
-  (is (= (draw (join line-separator ["  *  "
-                                     "  *  "
-                                     "*****"
-                                     "  *  "
-                                     "  *  "]))
-         (join line-separator [" 2*2 "
-                               "25*52"
-                               "*****"
-                               "25*52"
-                               " 2*2 "]))))
+  (:require [clojure.test :refer [deftest testing is]]
+            minesweeper))
 
+(def separator (System/getProperty "line.separator"))
+
+(defn join-with-line-separator
+  [coll]
+  (clojure.string/join separator coll))
+
+(deftest test-650ac4c0-ad6b-4b41-acde-e4ea5852c3b8
+  (testing "no columns"
+    (is (= (join-with-line-separator [""])
+           (minesweeper/draw
+             (join-with-line-separator [""]))))))
+
+(deftest test-6fbf8f6d-a03b-42c9-9a58-b489e9235478
+  (testing "no mines"
+    (is (= (join-with-line-separator ["   "
+                                      "   "
+                                      "   "])
+           (minesweeper/draw
+             (join-with-line-separator ["   "
+                                        "   "
+                                        "   "]))))))
+
+(deftest test-61aff1c4-fb31-4078-acad-cd5f1e635655
+  (testing "minefield with only mines"
+    (is (= (join-with-line-separator ["***"
+                                      "***"
+                                      "***"])
+           (minesweeper/draw
+             (join-with-line-separator ["***"
+                                        "***"
+                                        "***"]))))))
+
+(deftest test-84167147-c504-4896-85d7-246b01dea7c5
+  (testing "mine surrounded by spaces"
+    (is (= (join-with-line-separator ["111"
+                                      "1*1"
+                                      "111"])
+           (minesweeper/draw
+             (join-with-line-separator ["   "
+                                        " * "
+                                        "   "]))))))
+
+
+(deftest test-cb878f35-43e3-4c9d-93d9-139012cccc4a
+  (testing "space surrounded by mines"
+    (is (= (join-with-line-separator ["***"
+                                      "*8*"
+                                      "***"])
+           (minesweeper/draw
+             (join-with-line-separator ["***"
+                                        "* *"
+                                        "***"]))))))
+
+(deftest test-7037f483-ddb4-4b35-b005-0d0f4ef4606f
+  (testing "horizontal line"
+    (is (= (join-with-line-separator ["1*2*1"])
+           (minesweeper/draw
+             (join-with-line-separator [" * * "]))))))
+
+(deftest test-e359820f-bb8b-4eda-8762-47b64dba30a6
+  (testing "horizontal line, mines at edges"
+    (is (= (join-with-line-separator ["*1 1*"])
+           (minesweeper/draw
+             (join-with-line-separator ["*   *"]))))))
+
+(deftest test-c5198b50-804f-47e9-ae02-c3b42f7ce3ab
+  (testing "vertical line"
+    (is (= (join-with-line-separator ["1"
+                                      "*"
+                                      "2"
+                                      "*"
+                                      "1"])
+           (minesweeper/draw
+             (join-with-line-separator [" "
+                                        "*"
+                                        " "
+                                        "*"
+                                        " "]))))))
+
+(deftest test-0c79a64d-703d-4660-9e90-5adfa5408939
+  (testing "vertical line, mines at edges"
+    (is (= (join-with-line-separator ["*"
+                                      "1"
+                                      " "
+                                      "1"
+                                      "*"])
+           (minesweeper/draw
+             (join-with-line-separator ["*"
+                                        " "
+                                        " "
+                                        " "
+                                        "*"]))))))
+
+(deftest test-4b098563-b7f3-401c-97c6-79dd1b708f34
+  (testing "cross"
+    (is (= (join-with-line-separator [" 2*2 "
+                                      "25*52"
+                                      "*****"
+                                      "25*52"
+                                      " 2*2 "])
+           (minesweeper/draw
+             (join-with-line-separator ["  *  "
+                                        "  *  "
+                                        "*****"
+                                        "  *  "
+                                        "  *  "]))))))
+
+(deftest test-04a260f1-b40a-4e89-839e-8dd8525abe0e
+  (testing "large minefield"
+    (is (= (join-with-line-separator ["1*22*1"
+                                      "12*322"
+                                      " 123*2"
+                                      "112*4*"
+                                      "1*22*2"
+                                      "111111"])
+           (minesweeper/draw
+             (join-with-line-separator [" *  * "
+                                        "  *   "
+                                        "    * "
+                                        "   * *"
+                                        " *  * "
+                                        "      "]))))))
diff --git a/exercises/practice/pythagorean-triplet/.meta/tests.toml b/exercises/practice/pythagorean-triplet/.meta/tests.toml
new file mode 100644
index 0000000..719620a
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/.meta/tests.toml
@@ -0,0 +1,31 @@
+# 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.
+
+[a19de65d-35b8-4480-b1af-371d9541e706]
+description = "triplets whose sum is 12"
+
+[48b21332-0a3d-43b2-9a52-90b2a6e5c9f5]
+description = "triplets whose sum is 108"
+
+[dffc1266-418e-4daa-81af-54c3e95c3bb5]
+description = "triplets whose sum is 1000"
+
+[5f86a2d4-6383-4cce-93a5-e4489e79b186]
+description = "no matching triplets for 1001"
+
+[bf17ba80-1596-409a-bb13-343bdb3b2904]
+description = "returns all matching triplets"
+
+[9d8fb5d5-6c6f-42df-9f95-d3165963ac57]
+description = "several matching triplets"
+
+[f5be5734-8aa0-4bd1-99a2-02adcc4402b4]
+description = "triplets for large number"
diff --git a/exercises/practice/pythagorean-triplet/test/pythagorean_triplet_test.clj b/exercises/practice/pythagorean-triplet/test/pythagorean_triplet_test.clj
new file mode 100644
index 0000000..66742cc
--- /dev/null
+++ b/exercises/practice/pythagorean-triplet/test/pythagorean_triplet_test.clj
@@ -0,0 +1,46 @@
+(ns pythagorean-triplet-test
+  (:require [clojure.test :refer [deftest testing is]]
+            pythagorean-triplet))
+
+(deftest test-a19de65d-35b8-4480-b1af-371d9541e706
+  (testing "triplets whose sum is 12"
+    (is (= [[3 4 5]] (pythagorean-triplet/find-pythagorean-triplets 12)))))
+
+(deftest test-48b21332-0a3d-43b2-9a52-90b2a6e5c9f5
+  (testing "triplets whose sum is 108"
+    (is (= [[27 36 45]] (pythagorean-triplet/find-pythagorean-triplets 108)))))
+
+(deftest test-dffc1266-418e-4daa-81af-54c3e95c3bb5
+  (testing "triplets whose sum is 1000"
+    (is (= [[200 375 425]] (pythagorean-triplet/find-pythagorean-triplets 1000)))))
+
+(deftest test-5f86a2d4-6383-4cce-93a5-e4489e79b186
+  (testing "no matching triplets for 1001"
+    (is (= [] (pythagorean-triplet/find-pythagorean-triplets 1001)))))
+
+(deftest test-bf17ba80-1596-409a-bb13-343bdb3b2904
+  (testing "returns all matching triplets"
+    (is (= [[9 40 41]
+            [15 36 39]]
+           (pythagorean-triplet/find-pythagorean-triplets 90)))))
+
+(deftest test-9d8fb5d5-6c6f-42df-9f95-d3165963ac57
+  (testing "several matching triplets"
+    (is (= [[40 399 401]
+            [56 390 394]
+            [105 360 375]
+            [120 350 370]
+            [140 336 364]
+            [168 315 357]
+            [210 280 350]
+            [240 252 348]]
+           (pythagorean-triplet/find-pythagorean-triplets 840)))))
+
+(deftest test-f5be5734-8aa0-4bd1-99a2-02adcc4402b4
+  (testing "triplets for large number"
+    (is (= [[1200 14375 14425]
+            [1875 14000 14125]
+            [5000 12000 13000]
+            [6000 11250 12750]
+            [7500 10000 12500]]
+           (pythagorean-triplet/find-pythagorean-triplets 30000)))))
diff --git a/exercises/practice/spiral-matrix/test/spiral_matrix_test.clj b/exercises/practice/spiral-matrix/test/spiral_matrix_test.clj
index 85dc9f9..ea4533e 100644
--- a/exercises/practice/spiral-matrix/test/spiral_matrix_test.clj
+++ b/exercises/practice/spiral-matrix/test/spiral_matrix_test.clj
@@ -1,58 +1,43 @@
 (ns spiral-matrix-test
   (:require [clojure.test :refer [deftest is testing]]
-            [spiral-matrix :refer [spiral]]))
+            spiral-matrix))
 
-(deftest spiral-matrix-of-0
-  (testing "spiral matrix of 0"
-    (is (= (spiral 0)
-           '()))))
+(deftest test-8f584201-b446-4bc9-b132-811c8edd9040
+  (testing "Empty spiral"
+    (is (= []
+           (spiral-matrix/spiral 0)))))
 
-(deftest spiral-matrix-of-1
-  (testing "spiral matrix of 1"
-    (is (= (spiral 1)
-           '((1))))))
+(deftest test-e40ae5f3-e2c9-4639-8116-8a119d632ab2
+  (testing "Trivial spiral"
+    (is (= [[1]]
+           (spiral-matrix/spiral 1)))))
 
-(deftest spiral-matrix-of-2
-  (testing "spiral matrix of 2"
-    (is (= (spiral 2)
-           '((1 2)
-             (4 3))))))
+(deftest test-cf05e42d-eb78-4098-a36e-cdaf0991bc48
+  (testing "Spiral of size 2"
+    (is (= [[1 2]
+            [4 3]]
+           (spiral-matrix/spiral 2)))))
 
-(deftest spiral-matrix-of-3
-  (testing "spiral matrix of 3"
-    (is (= (spiral 3)
-           '((1 2 3)
-             (8 9 4)
-             (7 6 5))))))
+(deftest test-1c475667-c896-4c23-82e2-e033929de939
+  (testing "Spiral of size 3"
+    (is (= [[1 2 3]
+            [8 9 4]
+            [7 6 5]]
+           (spiral-matrix/spiral 3)))))
 
-(deftest spiral-matrix-of-4
-  (testing "spiral matrix of 4"
-    (is (= (spiral 4)
-           '((1  2  3  4)
-             (12 13 14 5)
-             (11 16 15 6)
-             (10 9  8  7))))))
+(deftest test-05ccbc48-d891-44f5-9137-f4ce462a759d
+  (testing "Spiral of size 4"
+    (is (= [[1  2  3  4]
+            [12 13 14 5]
+            [11 16 15 6]
+            [10 9  8  7]]
+           (spiral-matrix/spiral 4)))))
 
-(deftest spiral-matrix-of-20
-  (testing "spiral matrix of 20"
-    (is (= (spiral 20)
-           '((1  2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20)
-             (76 77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  21)
-             (75 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 95  22)
-             (74 143 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 161 96  23)
-             (73 142 203 256 257 258 259 260 261 262 263 264 265 266 267 268 219 162 97  24)
-             (72 141 202 255 300 301 302 303 304 305 306 307 308 309 310 269 220 163 98  25)
-             (71 140 201 254 299 336 337 338 339 340 341 342 343 344 311 270 221 164 99  26)
-             (70 139 200 253 298 335 364 365 366 367 368 369 370 345 312 271 222 165 100 27)
-             (69 138 199 252 297 334 363 384 385 386 387 388 371 346 313 272 223 166 101 28)
-             (68 137 198 251 296 333 362 383 396 397 398 389 372 347 314 273 224 167 102 29)
-             (67 136 197 250 295 332 361 382 395 400 399 390 373 348 315 274 225 168 103 30)
-             (66 135 196 249 294 331 360 381 394 393 392 391 374 349 316 275 226 169 104 31)
-             (65 134 195 248 293 330 359 380 379 378 377 376 375 350 317 276 227 170 105 32)
-             (64 133 194 247 292 329 358 357 356 355 354 353 352 351 318 277 228 171 106 33)
-             (63 132 193 246 291 328 327 326 325 324 323 322 321 320 319 278 229 172 107 34)
-             (62 131 192 245 290 289 288 287 286 285 284 283 282 281 280 279 230 173 108 35)
-             (61 130 191 244 243 242 241 240 239 238 237 236 235 234 233 232 231 174 109 36)
-             (60 129 190 189 188 187 186 185 184 183 182 181 180 179 178 177 176 175 110 37)
-             (59 128 127 126 125 124 123 122 121 120 119 118 117 116 115 114 113 112 111 38)
-             (58 57  56  55  54  53  52  51  50  49  48  47  46  45  44  43  42  41  40  39))))))
+(deftest test-f4d2165b-1738-4e0c-bed0-c459045ae50d
+  (testing "Spiral of size 5"
+    (is (= [[1  2  3  4  5]
+            [16 17 18 19 6]
+            [15 24 25 20 7]
+            [14 23 22 21 8]
+            [13 12 11 10 9]]
+           (spiral-matrix/spiral 5)))))
diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml
index b00c20a..1be425b 100644
--- a/exercises/practice/word-count/.meta/tests.toml
+++ b/exercises/practice/word-count/.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.
 
 [61559d5f-2cad-48fb-af53-d3973a9ee9ef]
 description = "count one word"
@@ -28,6 +35,11 @@ description = "normalize case"
 
 [4185a902-bdb0-4074-864c-f416e42a0f19]
 description = "with apostrophes"
+include = false
+
+[4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3]
+description = "with apostrophes"
+reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19"
 
 [be72af2b-8afe-4337-b151-b297202e4a7b]
 description = "with quotations"
@@ -40,3 +52,6 @@ description = "multiple spaces not detected as a word"
 
 [50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360]
 description = "alternating word separators not detected as a word"
+
+[6d00f1db-901c-4bec-9829-d20eb3044557]
+description = "quotation for word with apostrophe"
diff --git a/exercises/practice/word-count/test/word_count_test.clj b/exercises/practice/word-count/test/word_count_test.clj
index 772794a..b0afa84 100644
--- a/exercises/practice/word-count/test/word_count_test.clj
+++ b/exercises/practice/word-count/test/word_count_test.clj
@@ -1,27 +1,113 @@
 (ns word-count-test
-  (:require [clojure.test :refer [deftest is]]
+  (:require [clojure.test :refer [deftest testing is]]
             word-count))
 
-(deftest count-one-word
-  (is (= {"word" 1}
-         (word-count/word-count "word"))))
+(deftest test-61559d5f-2cad-48fb-af53-d3973a9ee9ef
+  (testing "count one word"
+    (is (= {"word" 1}
+           (word-count/word-count "word")))))
 
-(deftest count-one-of-each
-  (is (= {"one" 1 "of" 1 "each" 1}
-         (word-count/word-count "one of each"))))
+(deftest test-5abd53a3-1aed-43a4-a15a-29f88c09cbbd
+  (testing "count one of each word"
+    (is (= {"one" 1
+            "of" 1
+            "each" 1}
+           (word-count/word-count "one of each")))))
 
-(deftest count-multiple-occurrences
-  (is (= {"one" 1 "fish" 4 "two" 1 "red" 1 "blue" 1}
-         (word-count/word-count "one fish two fish red fish blue fish"))))
+(deftest test-2a3091e5-952e-4099-9fac-8f85d9655c0e
+  (testing "multiple occurrences of a word"
+    (is (= {"one" 1
+            "fish" 4
+            "two" 1
+            "red" 1
+            "blue" 1}
+           (word-count/word-count "one fish two fish red fish blue fish")))))
 
-(deftest ignore-punctuation
-  (is (= {"car" 1, "carpet" 1 "as" 1 "java" 1 "javascript" 1}
-         (word-count/word-count "car : carpet as java : javascript!!&@$%^&"))))
+(deftest test-e81877ae-d4da-4af4-931c-d923cd621ca6
+  (testing "handles cramped lists"
+    (is (= {"one" 1
+            "two" 1
+            "three" 1}
+           (word-count/word-count "one,two,three")))))
 
-(deftest include-numbers
-  (is (= {"testing" 2 "1" 1 "2" 1}
-         (word-count/word-count "testing, 1, 2 testing"))))
+(deftest test-7349f682-9707-47c0-a9af-be56e1e7ff30
+  (testing "handles expanded lists"
+    (is (= {"one" 1
+            "two" 1
+            "three" 1}
+           (word-count/word-count "one,\ntwo,\nthree")))))
 
-(deftest normalize-case
-  (is (= {"go" 3}
-         (word-count/word-count "go Go GO"))))
+(deftest test-a514a0f2-8589-4279-8892-887f76a14c82
+  (testing "ignore punctuation"
+    (is (= {"car" 1
+            "carpet" 1
+            "as" 1
+            "java" 1
+            "javascript" 1}
+           (word-count/word-count "car: carpet as java: javascript!!&@$%^&")))))
+
+(deftest test-d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e
+  (testing "include numbers"
+    (is (= {"testing" 2
+            "1" 1
+            "2" 1}
+           (word-count/word-count "testing, 1, 2 testing")))))
+
+(deftest test-dac6bc6a-21ae-4954-945d-d7f716392dbf
+  (testing "normalize case"
+    (is (= {"go" 3
+            "stop" 2}
+           (word-count/word-count "go Go GO Stop stop")))))
+
+(deftest test-4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3
+  (testing "with apostrophes"
+    (is (= {"first" 1
+            "don't" 2
+            "laugh" 1
+            "then" 1
+            "cry" 1
+            "you're" 1
+            "getting" 1
+            "it" 1}
+           (word-count/word-count "'First: don't laugh. Then: don't cry. You're getting it.'")))))
+
+(deftest test-be72af2b-8afe-4337-b151-b297202e4a7b
+  (testing "with quotations"
+    (is (= {"joe" 1
+            "can't" 1
+            "tell" 1
+            "between" 1
+            "large" 2
+            "and" 1}
+           (word-count/word-count "Joe can't tell between 'large' and large.")))))
+
+(deftest test-8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6
+  (testing "substrings from the beginning"
+    (is (= {"joe" 1
+            "can't" 1
+            "tell" 1
+            "between" 1
+            "app" 1
+            "apple" 1
+            "and" 1
+            "a" 1}
+           (word-count/word-count "Joe can't tell between app, apple and a.")))))
+
+(deftest test-c5f4ef26-f3f7-4725-b314-855c04fb4c13
+  (testing "multiple spaces not detected as a word"
+    (is (= {"multiple" 1
+            "whitespaces" 1}
+           (word-count/word-count " multiple   whitespaces")))))
+
+(deftest test-50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360
+  (testing "alternating word separators not detected as a word"
+    (is (= {"one" 1
+            "two" 1
+            "three" 1}
+           (word-count/word-count ",\n,one,\n ,two \n 'three'")))))
+
+(deftest test-6d00f1db-901c-4bec-9829-d20eb3044557
+  (testing "quotation for word with apostrophe"
+    (is (= {"can" 1
+            "can't" 2}
+           (word-count/word-count "can, can't, 'can't'")))))
diff --git a/test.clj b/test.clj
index afe33cd..e924bfe 100755
--- a/test.clj
+++ b/test.clj
@@ -30,18 +30,18 @@
 
 (defn test-exercise [slug]
   (let [practice? (contains? (set practice-exercises) slug)
+        type (if practice? "practice" "concept")
+        dir (str root "exercises/" type "/" slug "/")
         example (if practice?
-                  (str root "exercises/practice/" slug "/.meta/src/example.clj")
-                  (str root "exercises/concept/" slug "/.meta/exemplar.clj"))
-        src (if practice?
-              (str root "exercises/practice/" slug "/src/" (->snake_case slug) ".clj")
-              (str root "exercises/concept/" slug "/src/" (->snake_case slug) ".clj"))]
+                  (str dir ".meta/src/example.clj")
+                  (str dir ".meta/exemplar.clj"))
+        src (str dir "src/" (->snake_case slug) ".clj")]
     (shell/sh "cp" example src)
     (= "pass" ((json/parse-string
                 (:out (shell/sh (str test-runner-dir "test-runner.clj")
                                 slug
-                                (str root (if practice? "exercises/practice/" "exercises/concept/") slug "/")
-                                (str root (if practice? "exercises/practice/" "exercises/concept/") slug "/"))))
+                                dir
+                                dir)))
                "status"))))
 
 (defn test-exercises! []