diff --git a/api/gitlab_classes.py b/api/gitlab_classes.py
index 4ca6b635c0480751823758717a33742521b4287b..e1f12af13f57e3250acd8495b9978c17251c0e5f 100644
--- a/api/gitlab_classes.py
+++ b/api/gitlab_classes.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import datetime, date
 from functools import reduce
 from typing import ClassVar, Dict, Iterable, List, Optional, Set, Union
 
@@ -110,7 +110,7 @@ class GitlabIssue:
         """
         return self.git_issue.description
 
-    def get_state(self) -> str:
+    def get_state(self) -> str:     # TODO, deprecate and replace with is_opened & is_closed
         """
         :return: opened or closed
         """
@@ -160,6 +160,19 @@ class GitlabIssue:
         """
         return self.git_issue.web_url
 
+    def get_assignee(self) -> Optional[GitlabUser]:
+        assignee = self.git_issue.assignee
+        if assignee is None:
+            return None
+        else:
+            return GitlabUser(assignee['username'])
+
+    def get_participants(self) -> List[GitlabUser]:
+        participants = []
+        for participant in self.git_issue.participants():
+            participants.append(GitlabUser(participant['username']))
+        return participants
+
     def close(self) -> None:
         self.git_issue.state_event = 'close'
         self.git_issue.save()
@@ -177,7 +190,6 @@ class GitlabIssue:
     # milestone
     # assignees             list of users
     # author                user
-    # assignee              user
     # user_notes_count
     # merge_requests_count
     # upvotes
@@ -242,7 +254,7 @@ class GitlabCommit:
 
     # noinspection PyShadowingNames
     def get_diff_size_by_filetype(self) -> Dict[str, int]:
-        binary_like_text_files = {"css", "csv", "fxml", "html"}                             # TODO soft-code this
+        binary_like_text_files = {"css", "csv", "fxml", "html"}  # TODO soft-code this
         # noinspection SpellCheckingInspection
         binary_files = {"docx", "gif", "jpg", "jpeg", "pdf", "png", "pptx", "svg", "xlsx"}  # TODO this, too
         return_diff: Dict[str, int] = {}
@@ -290,7 +302,7 @@ class GitlabCommit:
     def is_well_formatted(self, subject_line_length=72, message_line_length=72) -> bool:
         lines: List[str] = self.get_message().rstrip('\n').split('\n')
         return self._number_of_lines_too_long(lines, subject_line_length, message_line_length) == 0 \
-            and self._has_blank_line_after_subject(lines)
+               and self._has_blank_line_after_subject(lines)
 
     def detail_formatting_problems(self, subject_line_length=72, message_line_length=72) -> str:
         lines: List[str] = self.get_message().rstrip('\n').split('\n')
@@ -353,6 +365,64 @@ class GitlabMilestone:
             issues.append(GitlabIssue(issue))
         return issues
 
+    def is_active(self) -> bool:
+        """
+        :return: True if the milestone is active; False if the milestone is closed
+        """
+        return self.gitlab_milestone.state == 'active'
+
+    def is_closed(self) -> bool:
+        """
+        :return: True if the milestone is closed; False if the milestone is active
+        """
+        return self.gitlab_milestone.state == 'closed'
+
+    def close(self) -> None:
+        self.gitlab_milestone.state_event = 'close'
+        self.gitlab_milestone.save()
+
+    def get_title(self) -> str:
+        """
+        :return: The milestone's title
+        """
+        return self.gitlab_milestone.title
+
+    def get_description(self) -> str:
+        """
+        :return: The milestone's description
+        """
+        return self.gitlab_milestone.description
+
+    def get_created_at(self) -> date:
+        """
+        :return: a date object representing the start date
+        """
+        date_segments: List[str] = self.gitlab_milestone.start_date.split('-')
+        year = int(date_segments[0])
+        month = int(date_segments[1])
+        day = int(date_segments[2])
+        return date(year, month, day)
+
+    def get_due_date(self) -> date:
+        """
+        :return: a date object representing the due date
+        """
+        date_segments: List[str] = self.gitlab_milestone.due_date.split('-')
+        year = int(date_segments[0])
+        month = int(date_segments[1])
+        day = int(date_segments[2])
+        return date(year, month, day)
+
+    def __repr__(self) -> str:
+        return self.get_title()
+
+    # other gitlab_milestone fields:
+    # id
+    # iid
+    # project_id
+    # updated_at
+    # created_at
+
 
 class GitlabProject:
     git_project: Project
diff --git a/commit_format_info.py b/commit_format_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f8761c1e3b8b08d3561d1ee436efaa2d9e684f0
--- /dev/null
+++ b/commit_format_info.py
@@ -0,0 +1,29 @@
+from api.gitlab_classes import *
+from course import Course
+
+if __name__ == '__main__':
+    projects = GitlabProject.get_projects_by_group(Course.gitlab_namespace)
+    projects = list(filter(lambda p: p.get_name().startswith('30pair'), projects))
+    projects.sort(key=lambda p: p.get_name())
+    for project in projects:
+        commits = project.get_commits()
+        print(f'\n\n.    >>>>    {project}    <<<<')
+        print(f'{len(commits)} commits on the master branch')
+        merges = 0
+        reverts = 0
+        well_formatted_commits = 0
+        malformatted_commits = []
+        for commit in commits:
+            if commit.is_merge():
+                merges += 1
+            elif commit.get_message().startswith('Revert'):
+                reverts += 1
+            elif commit.is_well_formatted():
+                well_formatted_commits += 1
+            else:
+                malformatted_commits.append(commit)
+        print(
+            f'{merges} merges, {reverts} reverts, {well_formatted_commits} well-formatted commits, '
+            f'and {len(malformatted_commits)} malformatted commits')
+        for commit in malformatted_commits:
+            print(f'{commit.detail_formatting_problems()} ({commit.get_author()})')
diff --git a/milestone_sniffer.py b/milestone_sniffer.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7130c396abf5396fca8d3e4145d5f3190a13e2d
--- /dev/null
+++ b/milestone_sniffer.py
@@ -0,0 +1,19 @@
+from api.gitlab_classes import *
+from course import Course
+
+if __name__ == '__main__':
+    projects = sorted(list(filter(lambda p: p.get_name().startswith('36team'),
+                                  GitlabProject.get_projects_by_group(Course.gitlab_namespace))),
+                      key=lambda p: p.get_name())
+    for project in projects:
+        milestones = sorted(project.get_milestones(), key=lambda m: m.get_title())
+        print('\n\n')
+        if len(milestones) == 0:
+            print(f'Project: {project} has no milestones')
+        else:
+            for milestone in milestones:
+                print(f'Project: {project}\tMilestone: {milestone}')
+                for issue in sorted(milestone.get_issues(), key=lambda i: i.get_project_issue_id()):
+                    print(f'\tIssue {issue}')
+                    print(f'\t\tCreated at {issue.get_created_at()}, assigned to {issue.get_assignee()}, updated at {issue.get_updated_at()}, closed at {issue.get_closed_at()}.')
+                    print(f'\t\tParticipants: {issue.get_participants()}')