trak_flow 0.1.3 → 0.2.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.fasterer.yml +10 -0
  3. data/.loki +33 -0
  4. data/.reek.yml +161 -0
  5. data/.rubocop.yml +187 -0
  6. data/Archspec.rb +45 -0
  7. data/CHANGELOG.md +52 -1
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +124 -93
  10. data/README.md +15 -2
  11. data/_typos.toml +17 -0
  12. data/docs/api/ruby-library.md +2 -2
  13. data/docs/cli/admin-commands.md +3 -3
  14. data/docs/core-concepts/overview.md +2 -2
  15. data/docs/getting-started/configuration.md +2 -2
  16. data/docs/getting-started/installation.md +22 -5
  17. data/docs/index.md +2 -2
  18. data/docs/mcp/integration.md +11 -8
  19. data/docs/mcp/overview.md +29 -8
  20. data/examples/cli_demo.sh +1 -1
  21. data/examples/mcp/Gemfile.lock +147 -61
  22. data/lib/trak_flow/cli/admin_commands.rb +77 -59
  23. data/lib/trak_flow/cli/config_commands.rb +9 -14
  24. data/lib/trak_flow/cli/dep_commands.rb +3 -3
  25. data/lib/trak_flow/cli/label_commands.rb +3 -3
  26. data/lib/trak_flow/cli/main_commands.rb +127 -94
  27. data/lib/trak_flow/cli/plan_commands.rb +32 -25
  28. data/lib/trak_flow/cli/workflow_commands.rb +5 -4
  29. data/lib/trak_flow/cli.rb +2 -2
  30. data/lib/trak_flow/config/defaults.yml +5 -1
  31. data/lib/trak_flow/config/section.rb +3 -5
  32. data/lib/trak_flow/config.rb +20 -34
  33. data/lib/trak_flow/graph/dependency_graph.rb +72 -69
  34. data/lib/trak_flow/mcp/resources/dependency_graph.rb +13 -9
  35. data/lib/trak_flow/mcp/resources/task_by_id.rb +4 -3
  36. data/lib/trak_flow/mcp/server.rb +77 -76
  37. data/lib/trak_flow/mcp/tools/task_create.rb +17 -17
  38. data/lib/trak_flow/models/task.rb +21 -17
  39. data/lib/trak_flow/storage/database.rb +26 -16
  40. data/lib/trak_flow/storage/jsonl.rb +60 -62
  41. data/lib/trak_flow/version.rb +1 -1
  42. data/lib/trak_flow.rb +0 -1
  43. data/trak_flow.gemspec +57 -0
  44. metadata +11 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 665da8b32b7c217ef2c5b0755caa9db4d9fd18fa6d9020f3b81f760f5a736c1b
4
- data.tar.gz: 2bc92ce53419eedd2fa9e619a76eb0100faaaf49fe8c66dad01007d430f71cf3
3
+ metadata.gz: 2606b6f366413bd7f63ff4052533e8786973b0c94418d82a3f51a1d70926c46b
4
+ data.tar.gz: 38d4bc84e1a7948342ab828d42c2c1b9cb1332ebc876554b7bbb310d3d26f8d3
5
5
  SHA512:
6
- metadata.gz: 6db8403e94db89fc472ba3feeae33e577c7b541630693f0f0365bd045e8d469e51c9cd86194e4ae75b0cc52edb26edff4109d9b93be3e351164875c1f0b631ee
7
- data.tar.gz: f14732aaa2058255ed1111dc0557025a613b0e2e3c6abce66eb3913b63c2618be2e7969532258ab0be60fbc0a90b6ec6f3f62cd0c6c4299dc65f27c62db721ba
6
+ metadata.gz: 0f0823fcb8d829781394546d5c494584a038314b4a9719a635150f6acea97b0302bd447effa6e5eb68018afb25ec4ea33b8092f0844bcec33cca77e1205318a9
7
+ data.tar.gz: 32de4536ea66637a5f4c0d8d0bdc3a016e63cf93b4064a850acaa094819b960d9a25603edceef74e7d2d577d3cf2923cf2d0f2a60c4131635815a343aa10e03d
data/.fasterer.yml ADDED
@@ -0,0 +1,10 @@
1
+ # Fasterer configuration for trak_flow.
2
+ speedups:
3
+ # Conflicts with RuboCop's Style/RedundantFetchBlock, which wins here:
4
+ # the flagged defaults are frozen literals, so the block form saves no
5
+ # allocation. Only expensive defaults would justify the block.
6
+ fetch_with_argument_vs_block: false
7
+ # Rejected on purpose: rewriting readable each_with_index line parsing
8
+ # (Jsonl#read_entities) as a manual while loop trades clarity for a
9
+ # micro-optimization in IO-bound code.
10
+ each_with_index_vs_while: false
data/.loki ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ # trak_flow — distributed task tracking system for Robots (CLI + MCP server).
3
+
4
+ # Shared MadBomber-level per-gem tasks (lifecycle, quality, git, docs).
5
+ # import_up walks up from this repo until it finds madbomber/dev/.
6
+ # Imported selectively — not "dev/*.loki" — so quality_rails.loki's
7
+ # Rails-only gates (Brakeman, RailsBestPractices, ActiveRecordDoctor)
8
+ # stay out of this non-Rails gem's quality run.
9
+ import_up "dev/gem_tasks.loki"
10
+ import_up "dev/quality.loki"
11
+ import_up "dev/git.loki"
12
+ import_up "dev/doc_tasks.loki"
13
+
14
+ class Tasks
15
+ @@gem_name ||= "trak_flow".freeze
16
+
17
+ header "trak_flow v#{gem_version} — distributed task tracking for Robots"
18
+
19
+ desc "Run tests and RuboCop (rake default)"
20
+ def all
21
+ sh "bundle exec rake"
22
+ end
23
+
24
+ desc "Run the tf CLI (pass through arguments, e.g. asgard tf list)"
25
+ def tf(*args)
26
+ sh "bundle exec bin/tf #{args.join(' ')}"
27
+ end
28
+
29
+ desc "Start the TrakFlow MCP server"
30
+ def mcp(*args)
31
+ sh "bundle exec bin/tf_mcp #{args.join(' ')}", exec: true
32
+ end
33
+ end
data/.reek.yml ADDED
@@ -0,0 +1,161 @@
1
+ ---
2
+ # Reek configuration for trak_flow, following the sqa_project/.reek.yml
3
+ # conventions used across the madbomber workspace.
4
+ detectors:
5
+
6
+ # Disabled: public methods are documented via comments above the method,
7
+ # not a module-level comment.
8
+ IrresponsibleModule:
9
+ enabled: false
10
+
11
+ # Disabled: private helpers that don't reference self are fine where they are.
12
+ UtilityFunction:
13
+ enabled: false
14
+
15
+ # Disabled: respond_to? duck-typing is intentional.
16
+ ManualDispatch:
17
+ enabled: false
18
+
19
+ # Disabled: attr_accessor is standard Ruby; public attributes are a design choice.
20
+ Attribute:
21
+ enabled: false
22
+
23
+ # Disabled: bang methods without non-bang counterparts are common.
24
+ MissingSafeMethod:
25
+ enabled: false
26
+
27
+ # Disabled: explicit nil checks are idiomatic Ruby.
28
+ NilCheck:
29
+ enabled: false
30
+
31
+ # Disabled: in this codebase the "envied" receivers are db handles, option
32
+ # hashes, and IO objects — the suggested "move it to another class" is not
33
+ # applicable to a Sequel dataset or a parsed-JSON hash.
34
+ FeatureEnvy:
35
+ enabled: false
36
+
37
+ # Disabled: config-driven dispatch (error_policy, orphan_handling) is this
38
+ # gem's deliberate API style — a case over a policy string.
39
+ ControlParameter:
40
+ enabled: false
41
+
42
+ # Disabled: boolean keyword flags (ephemeral:, include_closed:, ...) are
43
+ # idiomatic Ruby and part of the public API.
44
+ BooleanParameter:
45
+ enabled: false
46
+
47
+ # Disabled: mirrors the RuboCop Lint/UnusedMethodArgument setting — args
48
+ # kept for API/documentation shape.
49
+ UnusedParameters:
50
+ enabled: false
51
+
52
+ # Default is 1 — two-deep iteration (a hash of arrays, rows of columns) is
53
+ # idiomatic Ruby.
54
+ NestedIterators:
55
+ max_allowed_nesting: 2
56
+
57
+ # Default is 1 — fires whenever a method is called even twice. Raise to 2.
58
+ # Excluded: the `dataset = dataset.where(...) if ...` filter chain and the
59
+ # three separate conditional alter_table migrations are the pattern itself,
60
+ # not accidental repetition.
61
+ DuplicateMethodCall:
62
+ max_calls: 2
63
+ exclude:
64
+ - "TrakFlow::Storage::Database#apply_date_filters"
65
+ - "TrakFlow::Storage::Database#migrate_schema_if_needed!"
66
+
67
+ # Default is 5 — too tight for orchestrator/dispatch methods; flog (see
68
+ # dev/quality.loki) is the blocking complexity gate, so raw statement count
69
+ # only needs to catch outliers. Excluded: the Sequel schema DSL and the
70
+ # migration steps are one statement per column/index — flat and declarative.
71
+ TooManyStatements:
72
+ max_statements: 15
73
+ exclude:
74
+ - "TrakFlow::Storage::Database#create_tasks_table"
75
+ - "TrakFlow::Storage::Database#migrate_schema_if_needed!"
76
+
77
+ # Disabled: flags the same guard reused across a class's methods (the
78
+ # options[:json] output mode, File.exist? checks, `if deleted.positive?`
79
+ # after deletes). Extracting these into state objects adds indirection
80
+ # without removing the underlying condition.
81
+ RepeatedConditional:
82
+ enabled: false
83
+
84
+ # Default is 3 — too low for methods with several named inputs. The MCP
85
+ # tool #call methods mirror their declared MCP input schemas verbatim.
86
+ LongParameterList:
87
+ max_params: 5
88
+ overrides:
89
+ initialize:
90
+ max_params: 8
91
+ exclude:
92
+ - "TrakFlow::Mcp::Tools::TaskCreate#call"
93
+ - "TrakFlow::Mcp::Tools::TaskUpdate#call"
94
+
95
+ # Default is 15; CLI-command classes legitimately carry more methods
96
+ # after complexity-driven extraction. Excluded: Database is the single
97
+ # facade over all Sequel access; CLI is the Thor command surface.
98
+ TooManyMethods:
99
+ max_methods: 25
100
+ exclude:
101
+ - "TrakFlow::Storage::Database"
102
+ - "TrakFlow::CLI"
103
+
104
+ # Default is 4 — too low for state-heavy objects.
105
+ TooManyInstanceVariables:
106
+ max_instance_variables: 20
107
+
108
+ TooManyConstants:
109
+ max_constants: 8
110
+
111
+ DataClump:
112
+ max_copies: 2
113
+ min_clump_size: 3
114
+
115
+ # Accept standard Ruby short-name conventions for limited-scope variables.
116
+ UncommunicativeVariableName:
117
+ accept:
118
+ - _
119
+ - a
120
+ - b
121
+ - c
122
+ - d
123
+ - e
124
+ - f
125
+ - h
126
+ - i
127
+ - k
128
+ - l
129
+ - m
130
+ - n
131
+ - p
132
+ - q
133
+ - r
134
+ - s
135
+ - t
136
+ - v
137
+
138
+ UncommunicativeParameterName:
139
+ accept:
140
+ - e
141
+ - i
142
+ - f
143
+ - k
144
+ - v
145
+ - t
146
+ - r
147
+ - s
148
+ - m
149
+ - n
150
+ - q
151
+ - b
152
+ - p
153
+
154
+ exclude_paths:
155
+ - test
156
+ - examples
157
+ - docs
158
+ - coverage
159
+ - pkg
160
+ - site
161
+ - notes
data/.rubocop.yml ADDED
@@ -0,0 +1,187 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 4.0
5
+ Exclude:
6
+ - 'examples/**/*'
7
+ - 'docs/**/*'
8
+ - 'site/**/*'
9
+ - 'pkg/**/*'
10
+ - 'coverage/**/*'
11
+ - 'notes/**/*'
12
+ - 'vendor/**/*'
13
+
14
+ # ── Style: disabled cops ───────────────────────────────────────────────────
15
+ Style/StringLiterals:
16
+ Enabled: false
17
+
18
+ Style/StringLiteralsInInterpolation:
19
+ Enabled: false
20
+
21
+ Style/Documentation:
22
+ Enabled: false
23
+
24
+ # Ruby 4.0 freezes string literals by default
25
+ Style/FrozenStringLiteralComment:
26
+ Enabled: false
27
+
28
+ Style/IfUnlessModifier:
29
+ Enabled: false
30
+
31
+ Style/RescueModifier:
32
+ Enabled: false
33
+
34
+ Style/TrivialAccessors:
35
+ Enabled: false
36
+
37
+ Style/MultilineTernaryOperator:
38
+ Enabled: false
39
+
40
+ Style/SafeNavigation:
41
+ Enabled: false
42
+
43
+ Style/ClassAndModuleChildren:
44
+ Enabled: false
45
+
46
+ Style/RescueStandardError:
47
+ Enabled: false
48
+
49
+ # Both % and format/sprintf are acceptable
50
+ Style/FormatString:
51
+ Enabled: false
52
+
53
+ # String concatenation and interpolation are both acceptable
54
+ Style/StringConcatenation:
55
+ Enabled: false
56
+
57
+ # ── Layout ─────────────────────────────────────────────────────────────────
58
+ Layout/LineLength:
59
+ Max: 140
60
+
61
+ Layout/ExtraSpacing:
62
+ Enabled: false
63
+
64
+ Layout/HashAlignment:
65
+ Enabled: false
66
+
67
+ Layout/FirstHashElementIndentation:
68
+ Enabled: false
69
+
70
+ Layout/EmptyLineAfterGuardClause:
71
+ Enabled: false
72
+
73
+ # ── Naming ─────────────────────────────────────────────────────────────────
74
+ # Archspec.rb is the filename the archspec tool requires (like Gemfile/Rakefile)
75
+ Naming/FileName:
76
+ Exclude:
77
+ - 'Archspec.rb'
78
+
79
+ # Single-char params (c, e, n) are acceptable throughout
80
+ Naming/MethodParameterName:
81
+ Enabled: false
82
+
83
+ Naming/RescuedExceptionsVariableName:
84
+ Enabled: false
85
+
86
+ Naming/AccessorMethodName:
87
+ Enabled: false
88
+
89
+ Naming/PredicatePrefix:
90
+ Enabled: false
91
+
92
+ Naming/PredicateMethod:
93
+ Exclude:
94
+ - 'test/**/*'
95
+
96
+ # ── Lint: relax noisy cops on intentional patterns ─────────────────────────
97
+ Lint/EmptyBlock:
98
+ Exclude:
99
+ - 'test/**/*'
100
+
101
+ # Library and framework methods commonly accept args for API/documentation purposes
102
+ Lint/UnusedMethodArgument:
103
+ Enabled: false
104
+
105
+ Lint/ConstantDefinitionInBlock:
106
+ Exclude:
107
+ - 'Rakefile'
108
+ - 'test/**/*'
109
+
110
+ # ── Gemspec ────────────────────────────────────────────────────────────────
111
+ # trak_flow declares dev deps directly in the gemspec (not a Gemfile group).
112
+ # The Gemfile deliberately carries a few extra gems the gemspec must not:
113
+ # ruby_llm/ruby_llm-mcp and puma/rackup are opt-in for consumers (see the
114
+ # Gemfile comments), and fasterer/reek are quality-gate tools.
115
+ Gemspec/DevelopmentDependencies:
116
+ EnforcedStyle: gemspec
117
+ AllowedGems:
118
+ - ruby_llm
119
+ - ruby_llm-mcp
120
+ - puma
121
+ - rackup
122
+ - fasterer
123
+ - reek
124
+
125
+ Gemspec/RequiredRubyVersion:
126
+ Enabled: false
127
+
128
+ Gemspec/OrderedDependencies:
129
+ Enabled: false
130
+
131
+ # ── Metrics ────────────────────────────────────────────────────────────────
132
+ # Flog is the primary complexity gate (see dev/quality.loki flog_check) —
133
+ # these RuboCop thresholds catch only egregious outliers without duplicating
134
+ # flog's more precise per-method scoring.
135
+
136
+ Metrics/MethodLength:
137
+ Max: 35
138
+ CountAsOne:
139
+ - heredoc
140
+ - array
141
+ - hash
142
+ Exclude:
143
+ - 'test/**/*'
144
+ - 'bin/**/*'
145
+
146
+ Metrics/AbcSize:
147
+ Max: 40
148
+ Exclude:
149
+ - 'test/**/*'
150
+ - 'bin/**/*'
151
+
152
+ Metrics/ClassLength:
153
+ Max: 650
154
+ Exclude:
155
+ - 'test/**/*'
156
+ - 'bin/**/*'
157
+
158
+ Metrics/ModuleLength:
159
+ Max: 200
160
+ Exclude:
161
+ - 'test/**/*'
162
+ - 'bin/**/*'
163
+
164
+ Metrics/CyclomaticComplexity:
165
+ Max: 20
166
+ Exclude:
167
+ - 'test/**/*'
168
+ - 'bin/**/*'
169
+
170
+ Metrics/PerceivedComplexity:
171
+ Max: 20
172
+ Exclude:
173
+ - 'test/**/*'
174
+ - 'bin/**/*'
175
+
176
+ Metrics/ParameterLists:
177
+ Enabled: false
178
+
179
+ Metrics/BlockLength:
180
+ # Thor's no_commands wraps a whole group of helper methods in one block
181
+ AllowedMethods:
182
+ - no_commands
183
+ Exclude:
184
+ - 'Rakefile'
185
+ - '*.gemspec'
186
+ - 'test/**/*'
187
+ - 'bin/**/*'
data/Archspec.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TrakFlow's architecture: a layered gem with pure domain models at the
4
+ # bottom, storage and graph services in the middle, and two independent
5
+ # interface layers (Thor CLI and MCP server) on top.
6
+ #
7
+ # support (id_generator, time_parser) <- leaf utilities
8
+ # config <- leaf, used by everything
9
+ # models <- pure domain objects
10
+ # storage <- Sequel/JSONL persistence
11
+ # graph <- analysis over an injected db
12
+ # cli | mcp <- interfaces, unaware of each other
13
+
14
+ source "lib/**/*.rb"
15
+
16
+ component :support, in: %w[lib/trak_flow/id_generator.rb lib/trak_flow/time_parser.rb]
17
+ component :config, in: %w[lib/trak_flow/config.rb lib/trak_flow/config/**/*.rb]
18
+ component :models, in: "lib/trak_flow/models/**/*.rb"
19
+ component :storage, in: "lib/trak_flow/storage/**/*.rb"
20
+ component :graph, in: "lib/trak_flow/graph/**/*.rb"
21
+ component :cli, in: %w[lib/trak_flow/cli.rb lib/trak_flow/cli/**/*.rb]
22
+ component :mcp, in: %w[lib/trak_flow/mcp.rb lib/trak_flow/mcp/**/*.rb]
23
+
24
+ # Leaf utilities depend on nothing above them.
25
+ support.cannot_use :config, :models, :storage, :graph, :cli, :mcp
26
+ config.cannot_use :support, :models, :storage, :graph, :cli, :mcp
27
+
28
+ # Models are pure domain objects: attributes, validation, lifecycle. They
29
+ # must never reach into persistence or the interface layers.
30
+ models.cannot_use :storage, :graph, :cli, :mcp
31
+
32
+ # Storage maps rows to models. It must not know about the analysis layer
33
+ # or the interfaces built on top of it.
34
+ storage.cannot_use :graph, :cli, :mcp
35
+
36
+ # Graph analyses task data through the db handle it is given — it may read
37
+ # model constants, but must not construct storage or serve an interface.
38
+ graph.cannot_use :storage, :cli, :mcp
39
+
40
+ # CLI and MCP are peer interfaces over the same core. Neither may depend
41
+ # on the other, so either can be extracted or dropped independently.
42
+ cli.cannot_use :mcp
43
+ mcp.cannot_use :cli
44
+
45
+ no_cycles among: %i[support config models storage graph cli mcp]
data/CHANGELOG.md CHANGED
@@ -5,7 +5,58 @@ All notable changes to TrakFlow will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## Unreleased
8
+ ## [0.2.0] - 2026-09-08
9
+
10
+ ### Added
11
+
12
+ - Real `Archspec.rb` (replacing the `architecture :rails` placeholder)
13
+ encoding the gem's layering: leaf `support`/`config`, pure `models`,
14
+ `storage` above models, `graph` over an injected db handle, and `cli`/
15
+ `mcp` as peer interfaces that must not depend on each other. Enforced
16
+ by the `archspec_check` quality gate.
17
+
18
+ ### Changed
19
+
20
+ - The MCP HTTP transport is now Rack-server-agnostic. `Mcp::Server` no
21
+ longer hard-codes Puma: `start_http` boots the transport through the
22
+ generic `Rackup::Handler` interface, using (in priority order) the new
23
+ `handler:` argument, the new `mcp.handler` config setting
24
+ (`TF_MCP__HANDLER`), or Rackup's default lookup — which picks whatever
25
+ Rack server the bundle already carries. Any server that supports
26
+ `rack.hijack` works (Puma, Falcon, WEBrick, iodine).
27
+ - `Mcp::Server#rack_app` is now public API: applications that already run
28
+ their own Rack server can mount the MCP transport into it (e.g.
29
+ `map("/mcp") { run TrakFlow::Mcp::Server.new.rack_app }` in config.ru)
30
+ instead of letting TrakFlow start a second server.
31
+ - The CLI `output` helpers now `yield` instead of capturing the block and
32
+ calling it, avoiding a proc allocation on every command's output
33
+ (Fasterer's block-vs-yield finding, applied across all 7 command
34
+ classes). Its remaining suggestions are declined in `.fasterer.yml`
35
+ with reasons: the while-loop rewrite trades readability for nothing in
36
+ IO-bound code, and the fetch-with-block form conflicts with RuboCop's
37
+ Style/RedundantFetchBlock for literal defaults.
38
+ - Docs: corrected stale filenames (`issues.jsonl` → `tasks.jsonl`,
39
+ `config.json` → `config.yml`), MCP endpoint paths (`/mcp/sse`,
40
+ `/mcp/messages`), default port (3333), and environment variable names
41
+ (`TF_MCP__PORT`).
42
+
43
+ ### Fixed
44
+
45
+ - `examples/cli_demo.sh` aborted before its final section: it referenced
46
+ the pre-rename `.trak_flow/issues.jsonl` filename.
47
+
48
+ ## [0.1.4] - 2026-09-08
49
+
50
+ ### Changed
51
+
52
+ - `puma` and `rackup` are no longer gemspec dependencies. The MCP HTTP
53
+ transport lazy-loads them and raises a clear error with instructions
54
+ when they're missing; the stdio transport and the models/storage API
55
+ never needed them. Consumers that run the HTTP transport must add
56
+ `gem 'puma', '>= 7.2.1'` and `gem 'rackup'` to their own Gemfile.
57
+ This keeps a web server (and its CVE surface — CVE-2026-47736 /
58
+ CVE-2026-47737 affected puma ~> 6.0) out of consumers that only use
59
+ TrakFlow as a task database.
9
60
 
10
61
  ## [0.1.3] - 2026-01-03
11
62
 
data/Gemfile CHANGED
@@ -6,3 +6,15 @@ gemspec
6
6
 
7
7
  gem 'ruby_llm'
8
8
  gem 'ruby_llm-mcp'
9
+
10
+ group :development, :test do
11
+ # Optional HTTP-transport gems for the MCP server — kept out of the
12
+ # gemspec on purpose (consumers opt in); needed here so bin/tf_mcp's
13
+ # HTTP mode works during development.
14
+ gem 'puma', '>= 7.2.1'
15
+ gem 'rackup'
16
+
17
+ # Quality-gate tools invoked via `bundle exec` by the asgard quality task.
18
+ gem 'fasterer'
19
+ gem 'reek'
20
+ end