ruby-maat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.commitlintrc.json +44 -0
  3. data/.mailmap +3 -0
  4. data/.overcommit.yml +77 -0
  5. data/.release-please-config.json +33 -0
  6. data/.release-please-manifest.json +3 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +48 -0
  9. data/CHANGELOG.md +46 -0
  10. data/CI_CD_SETUP.md +180 -0
  11. data/CLAUDE.md +130 -0
  12. data/Dockerfile +40 -0
  13. data/README.md +444 -0
  14. data/README_RUBY.md +300 -0
  15. data/RELEASE_PLEASE_SETUP.md +198 -0
  16. data/RUBY_MAAT.md +227 -0
  17. data/Rakefile +12 -0
  18. data/doc/imgs/abs_churn_sample.png +0 -0
  19. data/doc/imgs/code_age_sample.png +0 -0
  20. data/doc/imgs/coupling_sample.png +0 -0
  21. data/doc/imgs/crime_cover.jpg +0 -0
  22. data/doc/imgs/tree_map_sample.png +0 -0
  23. data/doc/intro.md +3 -0
  24. data/exe/ruby-maat +6 -0
  25. data/lib/ruby_maat/analysis/authors.rb +47 -0
  26. data/lib/ruby_maat/analysis/base_analysis.rb +70 -0
  27. data/lib/ruby_maat/analysis/churn.rb +255 -0
  28. data/lib/ruby_maat/analysis/code_age.rb +53 -0
  29. data/lib/ruby_maat/analysis/commit_messages.rb +58 -0
  30. data/lib/ruby_maat/analysis/communication.rb +56 -0
  31. data/lib/ruby_maat/analysis/effort.rb +150 -0
  32. data/lib/ruby_maat/analysis/entities.rb +40 -0
  33. data/lib/ruby_maat/analysis/identity.rb +12 -0
  34. data/lib/ruby_maat/analysis/logical_coupling.rb +134 -0
  35. data/lib/ruby_maat/analysis/sum_of_coupling.rb +43 -0
  36. data/lib/ruby_maat/analysis/summary.rb +43 -0
  37. data/lib/ruby_maat/app.rb +143 -0
  38. data/lib/ruby_maat/change_record.rb +47 -0
  39. data/lib/ruby_maat/cli.rb +187 -0
  40. data/lib/ruby_maat/dataset.rb +205 -0
  41. data/lib/ruby_maat/groupers/layer_grouper.rb +67 -0
  42. data/lib/ruby_maat/groupers/team_mapper.rb +51 -0
  43. data/lib/ruby_maat/groupers/time_grouper.rb +70 -0
  44. data/lib/ruby_maat/output/csv_output.rb +65 -0
  45. data/lib/ruby_maat/parsers/base_parser.rb +63 -0
  46. data/lib/ruby_maat/parsers/git2_parser.rb +72 -0
  47. data/lib/ruby_maat/parsers/git_parser.rb +66 -0
  48. data/lib/ruby_maat/parsers/mercurial_parser.rb +64 -0
  49. data/lib/ruby_maat/parsers/perforce_parser.rb +77 -0
  50. data/lib/ruby_maat/parsers/svn_parser.rb +76 -0
  51. data/lib/ruby_maat/parsers/tfs_parser.rb +103 -0
  52. data/lib/ruby_maat/version.rb +5 -0
  53. data/lib/ruby_maat.rb +44 -0
  54. metadata +143 -0
data/README.md ADDED
@@ -0,0 +1,444 @@
1
+ # Ruby Maat
2
+
3
+ Ruby Maat is a command line tool used to mine and analyze data from version-control systems (VCS). It's a Ruby port of the original [Code Maat](https://github.com/adamtornhill/code-maat) by Adam Tornhill.
4
+
5
+ **Note:** The analyses have evolved into [CodeScene](https://codescene.io/), which automates all the analyses found in Ruby Maat and several new ones.
6
+
7
+ ## Drop-in Replacement for Code Maat
8
+
9
+ Ruby Maat is designed as a **drop-in replacement** for the original Code Maat. It supports:
10
+
11
+ - ✅ Identical command-line arguments
12
+ - ✅ Same VCS log file formats*
13
+ - ✅ Compatible CSV output format
14
+ - ✅ All original analysis types
15
+
16
+ `*` In theory. I've only tested with git.
17
+
18
+ Simply replace `java -jar code-maat.jar` with `ruby-maat` in your existing scripts.
19
+
20
+ ## Installation
21
+
22
+ ### Via RubyGems (Recommended)
23
+
24
+ ```bash
25
+ gem install ruby-maat
26
+ ```
27
+
28
+ ### Via Docker
29
+
30
+ ```bash
31
+ docker build -t ruby-maat .
32
+ docker run -v /path/to/your/logs:/data ruby-maat -l /data/logfile.log -c git2 -a summary
33
+ ```
34
+
35
+ ### From Source
36
+
37
+ ```bash
38
+ git clone https://github.com/viamin/ruby-maat.git
39
+ cd ruby-maat
40
+ bundle install
41
+ rake install
42
+ ```
43
+
44
+ ### Requirements
45
+
46
+ - Ruby 3.2 or later
47
+ - No external dependencies beyond the gem requirements
48
+
49
+ ## License
50
+
51
+ Distributed under the [GNU General Public License v3.0](http://www.gnu.org/licenses/gpl.html).
52
+
53
+ ## Usage
54
+
55
+ ### Basic Usage
56
+
57
+ ```bash
58
+ # Analyze Git repository
59
+ ruby-maat -l logfile.log -c git2 -a summary
60
+
61
+ # With specific analysis
62
+ ruby-maat -l logfile.log -c git2 -a coupling
63
+
64
+ # Write to file
65
+ ruby-maat -l logfile.log -c git2 -a authors -o results.csv
66
+ ```
67
+
68
+ ### Command Line Options
69
+
70
+ When invoked with `-h`, Ruby Maat prints its usage:
71
+
72
+ ```
73
+ Usage: ruby-maat -l log-file -c vcs-type [options]
74
+
75
+ Required:
76
+ -l, --log LOG Log file with input data
77
+ -c, --version-control VCS Input vcs module type: supports svn, git, git2, hg, p4, or tfs
78
+
79
+ Analysis:
80
+ -a, --analysis ANALYSIS The analysis to run (default: authors)
81
+ Available: abs-churn, age, author-churn, authors, communication,
82
+ coupling, entity-churn, entity-effort, entity-ownership,
83
+ fragmentation, identity, main-dev, main-dev-by-revs, messages,
84
+ refactoring-main-dev, revisions, soc, summary
85
+
86
+ Filtering:
87
+ -n, --min-revs MIN_REVS Minimum number of revisions (default: 5)
88
+ -m, --min-shared-revs MIN_SHARED Minimum shared revisions (default: 5)
89
+ -i, --min-coupling MIN_COUPLING Minimum coupling percentage (default: 30)
90
+ -x, --max-coupling MAX_COUPLING Maximum coupling percentage (default: 100)
91
+ -s, --max-changeset-size SIZE Maximum changeset size (default: 30)
92
+
93
+ Output:
94
+ -r, --rows ROWS Max rows in output
95
+ -o, --outfile OUTFILE Write the result to the given file name
96
+ --input-encoding ENCODING Specify an encoding other than UTF-8 for the log file
97
+
98
+ Other:
99
+ -h, --help Show this help message
100
+ --version Show version information
101
+ ```
102
+
103
+ ### Generating input data
104
+
105
+ Ruby Maat operates on log files from version-control systems. **Use the exact same commands as the original Code Maat.** The supported version-control systems are `git`, Mercurial (`hg`), `svn`, Perforce (`p4`), and Team Foundation Server (`tfs`). The log files are generated by using the version-control systems themselves as described in the following sections.
106
+
107
+ #### Preparations
108
+
109
+ To analyze our VCS data we need to define a temporal period of interest. Over time, many design issues do get fixed and we don't want old data to interfere with our current analysis of the code. To limit the data Ruby Maat will consider, use one of the following flags depending on your version-control system:
110
+
111
+ - *git:* Use the `--after=<date>` to specify the last date of interest. The `<date>` is given as `YYYY-MM-DD`.
112
+ - *hg:* Use the `--date` switch to specify the last date of interest. The value is given as `">YYYY-MM-DD"`.
113
+ - *svn:* Use the `-r` option to specify a range of interest, for example `-r {20130820}:HEAD`.
114
+ - *p4:* Use the `-m` option to specify the last specified number of changelists, for example `-m 1000`.
115
+ - *tfs:* Use the `/stopafter` option to specify the number of changesets, for example `/stopafter:1000`
116
+
117
+ #### ⚠️ Windows user? Use GitBASH when interacting with Ruby Maat
118
+
119
+ Ruby Maat expects its Git logs to have UNIX line endings. If you're on windows, then the simplest solution
120
+ is to interact with Git through a Git BASH shell that emulates a Linux environment. The Git BASH shell is distributed together with Git itself.
121
+
122
+ #### Generate a Subversion log file using the following command
123
+
124
+ svn log -v --xml > logfile.log -r {YYYYmmDD}:HEAD
125
+
126
+ #### Generate a git log file using the following command
127
+
128
+ The first options is the legacy format used in Your Code As A Crime Scene. Use the `-c git` parse option when running Ruby Maat.
129
+
130
+ git log --pretty=format:'[%h] %aN %ad %s' --date=short --numstat --after=YYYY-MM-DD > logfile.log
131
+
132
+ There's a second supported Git format as well. It's more tolerant and faster to parse, so please prefer it over the plain `git` format described above. Use the `-c git2` parse option when running Ruby Maat.
133
+
134
+ git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames --after=YYYY-MM-DD > logfile.log
135
+
136
+ Many codebases include third-party content or non-code artefacts, which might generate noise in the analyses.
137
+ You can exclude such content via git's pathspecs that limit paths on the command line.
138
+ For example, let's say you want to exclude everything in a `vendor/ folder`. You would then append the following pattern to the `git log` commands above:
139
+
140
+ -- . ":(exclude)vendor/*"
141
+
142
+ To exclude multiple folders, you just append more pathspecs:
143
+
144
+ -- . ":(exclude)vendor/" ":(exclude)test/"
145
+
146
+ #### Generate a Mercurial log file using the following command
147
+
148
+ hg log --template "rev: {rev} author: {author} date: {date|shortdate} files:\n{files %'{file}\n'}\n" --date ">YYYY-MM-DD"
149
+
150
+ #### Generate a Perforce log file using the following command
151
+
152
+ p4 changes -s submitted -m 5000 //depot/project/... | cut -d ' ' -f 2 | xargs -I commitid -n1 sh -c 'p4 describe -s commitid | grep -v "^\s*$" && echo ""'
153
+
154
+ #### Generate a TFS log file using the following command from a Developer command-prompt
155
+
156
+ ###### Note: The TFS CLI tool does not support custom date formatting. The parser currently only supports the en-us default: Friday, January 1, 2016 1:12:35 PM - you may need to adjust your system locale settings before using the following command
157
+
158
+ tf hist /path/to/workspace /noprompt /format:detailed /recursive
159
+
160
+ ### Running Ruby Maat
161
+
162
+ If you've installed the gem:
163
+
164
+ ruby-maat -l logfile.log -c <vcs>
165
+
166
+ If you've built a docker container, then you can run it as
167
+
168
+ docker run -v /home/xx/src/logs:/data -it ruby-maat -l /data/logfile.log -c <vcs>
169
+
170
+ where the /home/xx/src/logs is the host's directory containing the file logfile.log.
171
+
172
+ When invoked with `-h`, Ruby Maat prints its usage. (See the [Command Line Options](#command-line-options) section above for details.)
173
+
174
+ ### Optional: specify an encoding
175
+
176
+ By default, Ruby Maat expects your log files to be UTF-8. If you use another encoding, override the default with `--input-encoding`, for example `--input-encoding UTF-16BE`.
177
+
178
+ #### Generating a summary
179
+
180
+ When starting out, I find it useful to get an overview of the mined data. With the `summary` analysis, Ruby Maat produces such an overview:
181
+
182
+ ruby-maat -l logfile.log -c git -a summary
183
+
184
+ The resulting output is on csv format:
185
+
186
+ statistic, value
187
+ number-of-commits, 919
188
+ number-of-entities, 730
189
+ number-of-entities-changed, 3397
190
+ number-of-authors, 79
191
+
192
+ If you use the second Git format, just specify `git2` instead:
193
+
194
+ ruby-maat -l logfile2.log -c git2 -a summary
195
+
196
+ #### Mining organizational metrics
197
+
198
+ By default, Ruby Maat runs an analysis on the number of authors per module. The authors analysis is based on the idea that the more developers working on a module, the larger the communication challenges. The analysis is invoked with the following command:
199
+
200
+ ruby-maat -l logfile.log -c git
201
+
202
+ The resulting output is on CSV format:
203
+
204
+ entity, n-authors, n-revs
205
+ InfoUtils.java, 12, 60
206
+ BarChart.java, 7, 30
207
+ Page.java, 4, 27
208
+ ...
209
+
210
+ In example above, the first column gives us the name of module, the second the total number of distinct authors that have made commits on that module, and the third column gives us the total number of revisions of the module. Taken together, these metrics serve as predictors of defects and quality issues.
211
+
212
+ #### Mining logical coupling
213
+
214
+ Logical coupling refers to modules that tend to change together. Modules that are logically coupled have a hidden, implicit dependency between them such that a change to one of them leads to a predictable change in the coupled module. To analyze the logical coupling in a system, invoke Ruby Maat with the following arguments:
215
+
216
+ ruby-maat -l logfile.log -c git -a coupling
217
+
218
+ The resulting output is on CSV format:
219
+
220
+ entity, coupled, degree, average-revs
221
+ InfoUtils.java, Page.java, 78, 44
222
+ InfoUtils.java, BarChart.java, 62, 45
223
+ ...
224
+
225
+ In the example above, the first column (`entity`) gives us the name of the module, the second (`coupled`) gives us the name of a logically
226
+ coupled module, the third column (`degree`) gives us the coupling as a percentage (0-100), and finally `average-revs` gives us the average number of revisions
227
+ of the two modules.
228
+
229
+ To interpret the data, consider the `InfoUtils.java` module in the example output above.
230
+ The coupling tells us that each time it's modified, it's a 78% risk/chance that we'll have to change our `Page.java` module too.
231
+ Since there's probably no reason they should change together, the analysis points to a part of the code worth investigating as a potential target for a future refactoring.
232
+
233
+ *Advanced*: the coupling analysis also supports `--verbose-results`. In verbose mode, the coupling analysis also includes the number of revisions for each coupled entity together
234
+ with the number of shared revisions. The main use cases for this option are a) build custom filters to reduce noise, or b) research studies.
235
+
236
+ ### Calculate code age
237
+
238
+ The change frequency of code is a factor that should (but rarely do) drive the evolution of a software architecture. In general, you want to stabilize as much code as possible. A failure to stabilize means that you need to maintain a working knowledge of those parts of the code for the life-time of the system.
239
+
240
+ One way to measure the stability of a software architecture is by a code age analysis:
241
+
242
+ ruby-maat -l logfile.log -c git -a age
243
+
244
+ The `age` analysis grades each module based on the date of last change. The measurement unit is age in months. Here's how the result may look:
245
+
246
+ entity,age-months
247
+ src/code_maat/app/app.clj,2
248
+ project.clj,4
249
+ src/code_maat/parsers/perforce.clj,5
250
+ ...
251
+
252
+ By default, Ruby Maat uses the current date as starting point for a code age analysis. You specify a different start time with the command line argument `--age-time-now`.
253
+
254
+ By using the techniques from [Your Code as a Crime Scene](https://pragprog.com/book/atcrime/your-code-as-a-crime-scene) we visualize the system with each module marked-up by its age (the more `red`, the more recent changes to the code):
255
+
256
+ ![code age visualized](doc/imgs/code_age_sample.png).
257
+
258
+ ## Code churn measures
259
+
260
+ Code churn is related to post-release defects. Modules with higher churn tend to have more defects. There are several different aspects of code churn. I intend to support several of them in Code Maat.
261
+
262
+ ### Absolute churn
263
+
264
+ The absolute code churn numbers are calculated with the `-a abs-churn` option. Note that the option is only available for `git`. The analysis will output a CSV table with the churn accumulated per date:
265
+
266
+ date, added, deleted
267
+ 2013-08-09, 259, 20
268
+ 2013-08-19, 146, 77
269
+ 2013-08-21, 5, 6
270
+ 2013-08-20, 773, 121
271
+ 2013-08-30, 349, 185
272
+ ...
273
+
274
+ Visualizing the result allows us to spot general trends over time:
275
+
276
+ ![abs churn visualized](doc/imgs/abs_churn_sample.png).
277
+
278
+ ### Churn by author
279
+
280
+ The idea behind this analysis is to get an idea of the overall contributions by each individual. The analysis is invoked with the `-a author-churn` option. The result will be given as CSV:
281
+
282
+ author, added, deleted
283
+ Adam Tornhill, 13826, 1670
284
+ Some One Else, 123, 80
285
+ Mr Petersen, 3, 3
286
+ ...
287
+
288
+ And, of course, you wouldn't use this data for any performance evaluation; it wouldn't serve well (in case anything should be rewarded it would be a net deletion of code - there's too much of it in the world).
289
+
290
+ ### Churn by entity
291
+
292
+ The pre-release churn of a module is a good predictor of its number of post-release defects. Such an analysis is supported in Code Maat by the `-a entity-churn` option.
293
+
294
+ Note: Some research suggests that relative churn measures are better, while others don't find any significant differences. The metrics calculated by Code Maat are absolute for now because it's easier to calculate. I'm likely to include support for relative churn too.
295
+
296
+ ## Ownership patterns
297
+
298
+ Once we have mined the organizational metrics described above, we may find we have multiple developers working on the same modules. How is their effort distributed? Does a particular module have a major developer or is everyone contributing a small piece? Let's find out by running the `-a entity-ownership` analysis. This analysis gives us the following output:
299
+
300
+ entity, author, added, deleted
301
+ analysis/authors.clj, apt, 164, 98
302
+ analysis/authors.clj, qew, 81, 10
303
+ analysis/authors.clj, jt, 42, 32
304
+ analysis/entities.clj, apt, 72, 24
305
+ ...
306
+
307
+ Another ownership view is to consider the effort spent by individual authors on the different entities in the system. This analysis is run by the `-a entity-effort` option. The analysis gives us the following table:
308
+
309
+ entity, author, author-revs, total-revs
310
+ analysis/authors.clj, apt, 5, 10
311
+ analysis/authors.clj, qew, 3, 10
312
+ analysis/authors.clj, jt, 1, 10
313
+ analysis/authors.clj, apt, 1, 10
314
+ ...
315
+
316
+ This information may be a useful guide to find the right author to discuss functionality and potential refactorings with. Just note that the ownership metrics are sensitive to the same biases as the churn metrics; they're both heuristics and no absolute truths.
317
+
318
+ ## Temporal periods
319
+
320
+ Sometimes we'd like to find patterns that manifests themselves over multiple commits. Code Maat provides the `--temporal-period` switch that let you consider all commits within a day as a logical change. Just provide the switch and add a digit - in the future that digit may even mean something; Right now the aggregation is limited to commits within a single day.
321
+
322
+ ## Architectural level analyses
323
+
324
+ Using the `-g` flag lets you specify a mapping from individual files to logical components. This feature makes it possible to
325
+ scale the analyses to an architectural level and get hotspots, knowledge metrics, etc. on the level of sub-systems.
326
+
327
+ There are some sample mapping files in the `end_to_end` test folder, for
328
+ example [this one](https://github.com/adamtornhill/code-maat/blob/ebd2b757ae31510b5cf52d0e11fafa82a7e062d1/test/code_maat/end_to_end/regex-and-text-layers-definition.txt)
329
+
330
+ The format is `regex_pattern => logical_group_name`:
331
+
332
+ ```
333
+ src/Features/Core => Core
334
+ ^src\/.*\/.*Tests\.cs$ => CS Tests
335
+ ```
336
+
337
+ Code Maat takes everything that matches a regex and analyses it as a
338
+ holistic whole by aggregating all file contributions for the matches.
339
+
340
+ ### Intermediate results
341
+
342
+ Code Maat supports an `identity` analysis. By using this switch, Code Maat will output the intermediate parse result of the raw VCS file. This can be useful either as a debug aid or as input to other tools.
343
+
344
+ ## Limitations
345
+
346
+ Ruby Maat processes all its content in memory, which may not scale to very large input files. The recommendation is to limit the input by specifying a sensible start date using the VCS date filtering options (as discussed above, you want to do that anyway to avoid confounds in the analysis).
347
+
348
+ For extremely large repositories (>100k commits), the original Java/Clojure version may have better memory management, but Ruby Maat should handle most real-world repositories without issues.
349
+
350
+ ### Windows Compatibility
351
+
352
+ **Note for Windows users**: Ruby Maat currently has dependencies on native extensions (`numo-narray`) that may have compilation issues on Windows with certain Ruby versions. If you encounter installation problems:
353
+
354
+ 1. **Use WSL2** (Windows Subsystem for Linux) for the best experience
355
+ 2. **Use Docker** as an alternative: `docker run -v /path/to/logs:/data ruby-maat -l /data/logfile.log -c git2 -a summary`
356
+ 3. **Check the Issues page** for current workarounds and updates on Windows compatibility
357
+
358
+ ## Development
359
+
360
+ ### Contributing
361
+
362
+ 1. Fork the repository
363
+ 2. Create a feature branch (`git checkout -b my-new-feature`)
364
+ 3. **Set up git hooks** (recommended):
365
+
366
+ ```bash
367
+ # Install Overcommit git hooks (Ruby-native alternative to pre-commit)
368
+ bundle exec overcommit --install
369
+
370
+ # Note: If you encounter Ruby 3.4 compatibility issues, you can:
371
+ # 1. Use Ruby 3.3 or earlier, or
372
+ # 2. Run quality checks manually: bundle exec rspec && bundle exec standardrb
373
+ ```
374
+
375
+ 4. Make your changes following the existing code style
376
+ 5. Add tests for your changes
377
+ 6. **Use conventional commit messages**:
378
+
379
+ ```text
380
+ feat: add new analysis type for code complexity
381
+ fix: resolve parsing issue with binary files
382
+ docs: update installation instructions
383
+ test: add integration tests for coupling analysis
384
+ ```
385
+
386
+ 7. Run the test suite (`bundle exec rspec`)
387
+ 8. Run the linter (`bundle exec standardrb`)
388
+ 9. Commit your changes (git hooks will run automatically)
389
+ 10. Push to the branch (`git push origin my-new-feature`)
390
+ 11. Create a new Pull Request
391
+
392
+ #### Conventional Commit Format
393
+
394
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/) for automated versioning and changelog generation. **All new commits must follow this format**:
395
+
396
+ ```text
397
+ <type>[optional scope]: <description>
398
+
399
+ [optional body]
400
+
401
+ [optional footer(s)]
402
+ ```
403
+
404
+ **Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
405
+
406
+ **Scopes**: `analysis`, `parser`, `output`, `cli`, `dataset`, `grouper`, `core`, `deps`, `ci`
407
+
408
+ **Examples**:
409
+
410
+ - `feat(analysis): add new complexity analysis algorithm`
411
+ - `fix(parser): handle binary files correctly in git2 parser`
412
+ - `docs: update installation instructions for Windows users`
413
+ - `test(integration): add end-to-end tests for coupling analysis`
414
+
415
+ > **Note**: This project transitioned to conventional commits for Release Please automation. Historical commits may not follow this format, but all new contributions must use conventional commit messages.
416
+
417
+ ### Running Tests
418
+
419
+ ```bash
420
+ # Run all tests
421
+ bundle exec rspec
422
+
423
+ # Run specific test file
424
+ bundle exec rspec spec/ruby_maat/analysis/authors_spec.rb
425
+
426
+ # Run with coverage
427
+ bundle exec rspec --format documentation
428
+ ```
429
+
430
+ ### Code Style
431
+
432
+ This project uses [StandardRB](https://github.com/testdouble/standard) for Ruby style enforcement:
433
+
434
+ ```bash
435
+ # Check style
436
+ bundle exec standardrb
437
+
438
+ # Auto-fix style issues
439
+ bundle exec standardrb --fix
440
+ ```
441
+
442
+ ## Acknowledgments
443
+
444
+ Ruby Maat is a Ruby port of the original [Code Maat](https://github.com/adamtornhill/code-maat) by Adam Tornhill.