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
data/docs/mcp/overview.md CHANGED
@@ -18,20 +18,42 @@ require 'trak_flow'
18
18
  require 'trak_flow/mcp'
19
19
 
20
20
  # Start the MCP server over STDIO
21
- TrakFlow::Mcp::Server.new.run
21
+ TrakFlow::Mcp::Server.new.start_stdio
22
22
  ```
23
23
 
24
24
  ### HTTP/SSE Transport (For Remote Access)
25
25
 
26
+ Requires the optional `rackup` gem plus a Rack server that supports
27
+ `rack.hijack` (Puma, Falcon, WEBrick, ...) in your bundle.
28
+
26
29
  ```ruby
27
30
  require 'trak_flow'
28
31
  require 'trak_flow/mcp'
29
32
 
30
- # Start with HTTP/SSE transport
33
+ # Start with HTTP/SSE transport on whatever Rack server the bundle carries
31
34
  server = TrakFlow::Mcp::Server.new
32
- server.run_http(port: 9292)
35
+ server.start_http(port: 3333)
36
+
37
+ # Or name the server explicitly (also settable via config mcp.handler
38
+ # or the TF_MCP__HANDLER environment variable)
39
+ server.start_http(port: 3333, handler: "puma")
40
+ ```
41
+
42
+ ### Embedding in an Existing Rack Application
43
+
44
+ If your application already runs its own Rack server, don't start a second
45
+ one — mount the MCP transport as a plain Rack app:
46
+
47
+ ```ruby
48
+ # config.ru of the host application
49
+ map "/mcp" do
50
+ run TrakFlow::Mcp::Server.new.rack_app
51
+ end
33
52
  ```
34
53
 
54
+ One process, one server — the host's server choice (Puma, Falcon, ...)
55
+ applies, as long as it supports `rack.hijack` for SSE.
56
+
35
57
  ## Architecture
36
58
 
37
59
  ```mermaid
@@ -49,7 +71,7 @@ graph LR
49
71
 
50
72
  subgraph "TrakFlow Core"
51
73
  DB[(SQLite)]
52
- JSONL[issues.jsonl]
74
+ JSONL[tasks.jsonl]
53
75
  end
54
76
 
55
77
  LLM <--> Client
@@ -191,13 +213,12 @@ Currently, the MCP server does not implement authentication. For production use:
191
213
 
192
214
  ## Configuration
193
215
 
194
- Environment variables:
216
+ Environment variables (via [anyway_config](https://github.com/palkan/anyway_config), prefix `TF_`):
195
217
 
196
218
  | Variable | Description | Default |
197
219
  |----------|-------------|---------|
198
- | `TRAK_FLOW_DIR` | Data directory | `.trak_flow` |
199
- | `TRAK_FLOW_MCP_PORT` | HTTP port | `9292` |
200
- | `TRAK_FLOW_MCP_HOST` | HTTP host | `127.0.0.1` |
220
+ | `TF_MCP__PORT` | HTTP port | `3333` |
221
+ | `TF_MCP__HANDLER` | Rackup handler name for the HTTP server (e.g. `puma`, `falcon`) | auto-detect from bundle |
201
222
 
202
223
  ## Next Steps
203
224
 
data/examples/cli_demo.sh CHANGED
@@ -281,7 +281,7 @@ echo "The JSONL file is Git-friendly for version control."
281
281
  echo ""
282
282
 
283
283
  run_cmd "ls -la .trak_flow/"
284
- run_cmd "head -5 .trak_flow/issues.jsonl"
284
+ run_cmd "head -5 .trak_flow/tasks.jsonl"
285
285
 
286
286
  pause
287
287
 
@@ -1,14 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trak_flow (0.1.0)
4
+ trak_flow (0.1.4)
5
5
  anyway_config (~> 2.0)
6
6
  debug_me
7
7
  fast-mcp
8
8
  oj (~> 3.16)
9
9
  pastel (~> 0.8)
10
- puma (~> 6.0)
11
- rackup (~> 2.0)
12
10
  sequel (~> 5.0)
13
11
  sqlite3 (~> 2.0)
14
12
  thor (~> 1.3)
@@ -20,7 +18,7 @@ GEM
20
18
  specs:
21
19
  addressable (2.8.8)
22
20
  public_suffix (>= 2.0.2, < 8.0)
23
- ansi (1.5.0)
21
+ ansi (1.6.0)
24
22
  anyway_config (2.7.2)
25
23
  ruby-next-core (~> 1.0)
26
24
  ast (2.4.3)
@@ -59,6 +57,17 @@ GEM
59
57
  dry-inflector (~> 1.0)
60
58
  dry-logic (~> 1.4)
61
59
  zeitwerk (~> 2.6)
60
+ event_stream_parser (1.0.0)
61
+ faraday (2.14.3)
62
+ faraday-net_http (>= 2.0, < 3.5)
63
+ json
64
+ logger
65
+ faraday-multipart (1.2.0)
66
+ multipart-post (~> 2.0)
67
+ faraday-net_http (3.4.4)
68
+ net-http (~> 0.5)
69
+ faraday-retry (2.4.0)
70
+ faraday (~> 2.0)
62
71
  fast-mcp (1.6.0)
63
72
  addressable (~> 2.8)
64
73
  base64
@@ -66,66 +75,113 @@ GEM
66
75
  json (~> 2.0)
67
76
  mime-types (~> 3.4)
68
77
  rack (>= 2.0, < 4.0)
78
+ fasterer (0.11.0)
79
+ ruby_parser (>= 3.19.1)
80
+ hana (1.3.7)
81
+ http-2 (1.2.2)
82
+ httpx (1.8.3)
83
+ http-2 (>= 1.2.0)
69
84
  json (2.18.0)
70
- language_server-protocol (3.17.0.5)
85
+ json-schema (6.2.0)
86
+ addressable (~> 2.8)
87
+ bigdecimal (>= 3.1, < 5)
88
+ json_schemer (2.5.0)
89
+ bigdecimal
90
+ hana (~> 1.3)
91
+ regexp_parser (~> 2.0)
92
+ simpleidn (~> 0.2)
93
+ language_server-protocol (3.17.0.6)
71
94
  lint_roller (1.1.0)
72
95
  logger (1.7.0)
96
+ marcel (1.2.1)
73
97
  mime-types (3.7.0)
74
98
  logger
75
99
  mime-types-data (~> 3.2025, >= 3.2025.0507)
76
100
  mime-types-data (3.2025.0924)
77
101
  minitest (5.27.0)
78
- minitest-reporters (1.7.1)
102
+ minitest-reporters (1.8.0)
79
103
  ansi
80
104
  builder
81
- minitest (>= 5.0)
105
+ minitest (>= 5.0, < 7)
82
106
  ruby-progressbar
107
+ multipart-post (2.4.1)
108
+ net-http (0.9.1)
109
+ uri (>= 0.11.1)
83
110
  nio4r (2.7.5)
84
111
  oj (3.16.13)
85
112
  bigdecimal (>= 3.0)
86
113
  ostruct (>= 0.2)
87
114
  ostruct (0.6.3)
88
- parallel (1.27.0)
89
- parser (3.3.10.0)
115
+ parallel (2.2.0)
116
+ parser (3.3.12.0)
90
117
  ast (~> 2.4.1)
91
118
  racc
92
119
  pastel (0.8.0)
93
120
  tty-color (~> 0.5)
94
- prism (1.7.0)
95
- public_suffix (7.0.0)
96
- puma (6.6.1)
121
+ prism (1.9.0)
122
+ public_suffix (7.0.5)
123
+ puma (8.0.2)
97
124
  nio4r (~> 2.0)
98
125
  racc (1.8.1)
99
126
  rack (3.2.4)
100
127
  rackup (2.3.1)
101
128
  rack (>= 3)
102
129
  rainbow (3.1.1)
103
- rake (13.3.1)
130
+ rake (13.4.2)
131
+ reek (6.5.0)
132
+ dry-schema (~> 1.13)
133
+ logger (~> 1.6)
134
+ parser (~> 3.3.0)
135
+ rainbow (>= 2.0, < 4.0)
136
+ rexml (~> 3.1)
104
137
  regexp_parser (2.11.3)
105
- rubocop (1.82.1)
106
- json (~> 2.3)
138
+ rexml (3.4.4)
139
+ rubocop (1.90.0)
140
+ json (>= 2.3)
107
141
  language_server-protocol (~> 3.17.0.2)
108
142
  lint_roller (~> 1.1.0)
109
- parallel (~> 1.10)
143
+ parallel (>= 1.10)
110
144
  parser (>= 3.3.0.2)
111
145
  rainbow (>= 2.2.2, < 4.0)
112
146
  regexp_parser (>= 2.9.3, < 3.0)
113
- rubocop-ast (>= 1.48.0, < 2.0)
147
+ rubocop-ast (>= 1.49.0, < 2.0)
114
148
  ruby-progressbar (~> 1.7)
115
149
  unicode-display_width (>= 2.4.0, < 4.0)
116
- rubocop-ast (1.49.0)
150
+ rubocop-ast (1.50.0)
117
151
  parser (>= 3.3.7.2)
118
152
  prism (~> 1.7)
119
153
  ruby-next-core (1.1.2)
120
154
  ruby-progressbar (1.13.0)
155
+ ruby_llm (1.16.0)
156
+ base64
157
+ event_stream_parser (~> 1)
158
+ faraday (>= 1.10.0)
159
+ faraday-multipart (>= 1)
160
+ faraday-net_http (>= 1)
161
+ faraday-retry (>= 1)
162
+ marcel (~> 1)
163
+ ruby_llm-schema (~> 0)
164
+ zeitwerk (~> 2)
165
+ ruby_llm-mcp (1.0.1)
166
+ httpx (~> 1.4)
167
+ json-schema (>= 5.0, < 7)
168
+ json_schemer (~> 2.4)
169
+ ruby_llm (~> 1.9)
170
+ zeitwerk (~> 2)
171
+ ruby_llm-schema (0.4.0)
172
+ ruby_parser (3.22.0)
173
+ racc (~> 1.5)
174
+ sexp_processor (~> 4.16)
121
175
  sequel (5.100.0)
122
176
  bigdecimal
177
+ sexp_processor (4.17.5)
123
178
  simplecov (0.22.0)
124
179
  docile (~> 1.1)
125
180
  simplecov-html (~> 0.11)
126
181
  simplecov_json_formatter (~> 0.1)
127
182
  simplecov-html (0.13.2)
128
183
  simplecov_json_formatter (0.1.4)
184
+ simpleidn (0.3.0)
129
185
  sqlite3 (2.9.0-arm64-darwin)
130
186
  strings (0.2.1)
131
187
  strings-ansi (~> 0.2)
@@ -144,6 +200,7 @@ GEM
144
200
  tty-screen (~> 0.8)
145
201
  unicode-display_width (2.6.0)
146
202
  unicode_utils (1.4.0)
203
+ uri (1.1.1)
147
204
  zeitwerk (2.7.4)
148
205
 
149
206
  PLATFORMS
@@ -151,75 +208,104 @@ PLATFORMS
151
208
 
152
209
  DEPENDENCIES
153
210
  bundler
211
+ fasterer
154
212
  minitest (~> 5.0)
155
213
  minitest-reporters (~> 1.6)
214
+ puma (>= 7.2.1)
215
+ rackup
156
216
  rake (~> 13.0)
217
+ reek
157
218
  rubocop (~> 1.0)
219
+ ruby_llm
220
+ ruby_llm-mcp
158
221
  simplecov (~> 0.22)
159
222
  trak_flow!
160
223
 
161
224
  CHECKSUMS
162
225
  addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
163
- ansi (1.5.0)
164
- anyway_config (2.7.2)
226
+ ansi (1.6.0)
227
+ anyway_config (2.7.2) sha256=30f6b087c0b41afdd43fe46c81d65a16f052a8489dab453abaeb4ea67aa74bad
165
228
  ast (2.4.3)
166
229
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
167
230
  bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
168
231
  builder (3.3.0)
169
- concurrent-ruby (1.3.6)
170
- debug_me (1.1.3)
232
+ concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
233
+ debug_me (1.1.3) sha256=ae8cf61993350d9b3cbe337a7f9a0f595be5b4976c10891a4e1eacc63d09f717
171
234
  docile (1.4.1)
172
- dry-configurable (1.3.0)
173
- dry-core (1.2.0)
174
- dry-inflector (1.2.0)
175
- dry-initializer (3.2.0)
176
- dry-logic (1.6.0)
177
- dry-schema (1.14.1)
178
- dry-types (1.8.3)
179
- fast-mcp (1.6.0)
235
+ dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8
236
+ dry-core (1.2.0) sha256=0cc5a7da88df397f153947eeeae42e876e999c1e30900f3c536fb173854e96a1
237
+ dry-inflector (1.2.0) sha256=22f5d0b50fd57074ae57e2ca17e3b300e57564c218269dcf82ff3e42d3f38f2e
238
+ dry-initializer (3.2.0) sha256=37d59798f912dc0a1efe14a4db4a9306989007b302dcd5f25d0a2a20c166c4e3
239
+ dry-logic (1.6.0) sha256=da6fedbc0f90fc41f9b0cc7e6f05f5d529d1efaef6c8dcc8e0733f685745cea2
240
+ dry-schema (1.14.1) sha256=2fcd7539a7099cacae6a22f6a3a2c1846fe5afeb1c841cde432c89c6cb9b9ff1
241
+ dry-types (1.8.3) sha256=b5d97a45e0ed273131c0c3d5bc9f5633c2d1242e092ee47401ce7d5eab65c1bc
242
+ event_stream_parser (1.0.0) sha256=a2683bab70126286f8184dc88f7968ffc4028f813161fb073ec90d171f7de3c8
243
+ faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
244
+ faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
245
+ faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
246
+ faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe
247
+ fast-mcp (1.6.0) sha256=d68abb45d2daab9e7ae2934417460e4bf9ac87493c585dc5bb626f1afb7d12c4
248
+ fasterer (0.11.0)
249
+ hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
250
+ http-2 (1.2.2) sha256=81b5d45f50fd4cd5f8c5d09651184bec9401e3ef169c3eb3e5b003d5614a92b9
251
+ httpx (1.8.3) sha256=cb88f2285c4ef17164d803a640dc393d28722cba7f282ad0e7f9f926cd02dc47
180
252
  json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
181
- language_server-protocol (3.17.0.5)
253
+ json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
254
+ json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
255
+ language_server-protocol (3.17.0.6)
182
256
  lint_roller (1.1.0)
183
257
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
184
- mime-types (3.7.0)
185
- mime-types-data (3.2025.0924)
258
+ marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f
259
+ mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
260
+ mime-types-data (3.2025.0924) sha256=f276bca15e59f35767cbcf2bc10e023e9200b30bd6a572c1daf7f4cc24994728
186
261
  minitest (5.27.0)
187
- minitest-reporters (1.7.1)
262
+ minitest-reporters (1.8.0)
263
+ multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
264
+ net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
188
265
  nio4r (2.7.5)
189
- oj (3.16.13)
190
- ostruct (0.6.3)
191
- parallel (1.27.0)
192
- parser (3.3.10.0)
193
- pastel (0.8.0)
194
- prism (1.7.0)
195
- public_suffix (7.0.0) sha256=f7090b5beb0e56f9f10d79eed4d5fbe551b3b425da65877e075dad47a6a1b095
196
- puma (6.6.1)
266
+ oj (3.16.13) sha256=b114bcb83ef884f1736b3112108f77cf9ca90aa8111a775318cc5d7a6a1e0303
267
+ ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
268
+ parallel (2.2.0)
269
+ parser (3.3.12.0)
270
+ pastel (0.8.0) sha256=481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75
271
+ prism (1.9.0)
272
+ public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
273
+ puma (8.0.2)
197
274
  racc (1.8.1)
198
- rack (3.2.4)
275
+ rack (3.2.4) sha256=5d74b6f75082a643f43c1e76b419c40f0e5527fcfee1e669ac1e6b73c0ccb6f6
199
276
  rackup (2.3.1)
200
277
  rainbow (3.1.1)
201
- rake (13.3.1)
202
- regexp_parser (2.11.3)
203
- rubocop (1.82.1)
204
- rubocop-ast (1.49.0)
205
- ruby-next-core (1.1.2)
278
+ rake (13.4.2)
279
+ reek (6.5.0)
280
+ regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
281
+ rexml (3.4.4)
282
+ rubocop (1.90.0)
283
+ rubocop-ast (1.50.0)
284
+ ruby-next-core (1.1.2) sha256=1b095fe2e45929f2581b3ecdb8d92b601ce4d72c12a466057c10e23b6f0c4512
206
285
  ruby-progressbar (1.13.0)
207
- sequel (5.100.0)
286
+ ruby_llm (1.16.0) sha256=26bd5310cf2ce55f74a60f8aae0b0d0327b586ff4532c84828103c3b2b905a18
287
+ ruby_llm-mcp (1.0.1) sha256=22121bfdea1874935debdd9cee0235d02473885ce8347cf745a323f0707fd248
288
+ ruby_llm-schema (0.4.0) sha256=e930f5a5316f9301bff3fb7fe572e44727d05bb8e50621001bbb49a47d63b8da
289
+ ruby_parser (3.22.0)
290
+ sequel (5.100.0) sha256=cb0329b62287a01db68eead46759c14497a3fae01b174e2c41da108a9e9b4a12
291
+ sexp_processor (4.17.5)
208
292
  simplecov (0.22.0)
209
293
  simplecov-html (0.13.2)
210
294
  simplecov_json_formatter (0.1.4)
211
- sqlite3 (2.9.0-arm64-darwin)
212
- strings (0.2.1)
213
- strings-ansi (0.2.0)
214
- thor (1.4.0)
215
- trak_flow (0.1.0)
216
- tty-color (0.6.0)
217
- tty-cursor (0.7.1)
218
- tty-screen (0.8.2)
219
- tty-spinner (0.9.3)
220
- tty-table (0.12.0)
221
- unicode-display_width (2.6.0)
222
- unicode_utils (1.4.0)
295
+ simpleidn (0.3.0) sha256=12ca730bed2f3db04d11e9bfd1bca3e11fb37f55b21eb2e9793fb5814bf54d03
296
+ sqlite3 (2.9.0-arm64-darwin) sha256=a917bd9b84285766ff3300b7d79cd583f5a067594c8c1263e6441618c04a6ed3
297
+ strings (0.2.1) sha256=933293b3c95cf85b81eb44b3cf673e3087661ba739bbadfeadf442083158d6fb
298
+ strings-ansi (0.2.0) sha256=90262d760ea4a94cc2ae8d58205277a343409c288cbe7c29416b1826bd511c88
299
+ thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d
300
+ trak_flow (0.1.4)
301
+ tty-color (0.6.0) sha256=6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a
302
+ tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
303
+ tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
304
+ tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
305
+ tty-table (0.12.0) sha256=fdc27a4750835c1a16efe19a0b857e3ced3652cc7aceafe6dca94908965b9939
306
+ unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
307
+ unicode_utils (1.4.0) sha256=b922d0cf2313b6b7136ada6645ce7154ffc86418ca07d53b058efe9eb72f2a40
308
+ uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
223
309
  zeitwerk (2.7.4) sha256=2bef90f356bdafe9a6c2bd32bcd804f83a4f9b8bc27f3600fff051eb3edcec8b
224
310
 
225
311
  BUNDLED WITH
@@ -13,33 +13,21 @@ module TrakFlow
13
13
  option :cascade, type: :boolean, default: false, desc: "Also delete children"
14
14
  def cleanup
15
15
  with_database do |db|
16
- cutoff = Time.now.utc - (options[:older_than] * 24 * 60 * 60)
17
- candidates = db.list_tasks(status: "closed", include_tombstones: true)
18
- .select { |i| i.closed_at && i.closed_at < cutoff }
16
+ candidates = cleanup_candidates(db)
17
+ count = candidates.size
19
18
 
20
19
  if candidates.empty?
21
20
  puts "No tasks to clean up"
22
21
  return
23
22
  end
24
23
 
25
- if options[:dry_run]
26
- puts "Would delete #{candidates.size} task(s):"
27
- candidates.each { |i| puts " #{i.id}: #{i.title}" }
28
- return
29
- end
24
+ return print_cleanup_dry_run(candidates) if options[:dry_run]
25
+ return unless options[:force] || confirm_cleanup?(count)
30
26
 
31
- unless options[:force]
32
- puts "About to delete #{candidates.size} task(s). Continue? (y/n)"
33
- return unless $stdin.gets.strip.downcase == "y"
34
- end
35
-
36
- candidates.each do |task|
37
- db.child_tasks(task.id).each { |c| db.delete_task(c.id) } if options[:cascade]
38
- db.delete_task(task.id)
39
- end
27
+ delete_candidates(db, candidates)
40
28
 
41
- output({ deleted: candidates.size }) do
42
- puts "Deleted #{candidates.size} task(s)"
29
+ output({ deleted: count }) do
30
+ puts "Deleted #{count} task(s)"
43
31
  end
44
32
  end
45
33
  end
@@ -49,28 +37,13 @@ module TrakFlow
49
37
  option :apply, type: :boolean, default: false, desc: "Apply compaction"
50
38
  def compact
51
39
  with_database do |db|
52
- stats = {
53
- total_tasks: db.all_task_ids.size,
54
- closed_tasks: db.list_tasks(status: "closed", include_tombstones: true).size,
55
- ephemeral: db.find_ephemeral_workflows.size,
56
- plans: db.find_plans.size,
57
- workflows: db.find_workflows.size
58
- }
59
-
60
40
  if options[:analyze]
41
+ stats = compaction_stats(db)
61
42
  output(stats) do
62
43
  stats.each { |k, v| puts "#{k}: #{v}" }
63
44
  end
64
- return
65
- end
66
-
67
- if options[:apply]
68
- db.list_tasks(status: "closed").each do |task|
69
- next unless task.closed_at && task.closed_at < (Time.now.utc - 30 * 24 * 60 * 60)
70
-
71
- task.status = "tombstone"
72
- db.update_task(task)
73
- end
45
+ elsif options[:apply]
46
+ tombstone_old_closed_tasks(db)
74
47
  puts "Compaction complete"
75
48
  else
76
49
  puts "Use --analyze to see stats or --apply to compact"
@@ -87,13 +60,13 @@ module TrakFlow
87
60
  dep_graph = Graph::DependencyGraph.new(db)
88
61
 
89
62
  graph_output = case options[:format]
90
- when "svg" then dep_graph.to_svg(include_closed: options[:include_closed])
91
- else dep_graph.to_dot(include_closed: options[:include_closed])
92
- end
63
+ when "svg" then dep_graph.to_svg(include_closed: options[:include_closed])
64
+ else dep_graph.to_dot(include_closed: options[:include_closed])
65
+ end
93
66
 
94
- if options[:output]
95
- File.write(options[:output], graph_output)
96
- puts "Graph written to #{options[:output]}"
67
+ if (output_path = options[:output])
68
+ File.write(output_path, graph_output)
69
+ puts "Graph written to #{output_path}"
97
70
  else
98
71
  puts graph_output
99
72
  end
@@ -103,32 +76,77 @@ module TrakFlow
103
76
  desc "analyze", "Analyze the task graph"
104
77
  def analyze
105
78
  with_database do |db|
106
- dep_graph = Graph::DependencyGraph.new(db)
107
- analysis = dep_graph.analyze
108
-
109
- output(analysis) do
110
- analysis.each do |k, v|
111
- if v.is_a?(Array)
112
- puts "#{k}:"
113
- v.each { |item| puts " - #{item}" }
114
- else
115
- puts "#{k}: #{v}"
116
- end
117
- end
118
- end
79
+ analysis = Graph::DependencyGraph.new(db).analyze
80
+ output(analysis) { print_analysis(analysis) }
119
81
  end
120
82
  end
121
83
 
122
84
  private
123
85
 
86
+ def print_analysis(analysis)
87
+ analysis.each do |k, v|
88
+ if v.is_a?(Array)
89
+ puts "#{k}:"
90
+ v.each { |item| puts " - #{item}" }
91
+ else
92
+ puts "#{k}: #{v}"
93
+ end
94
+ end
95
+ end
96
+
97
+ # Closed tasks (tombstones included) whose closed_at is older than --older-than days.
98
+ def cleanup_candidates(db)
99
+ cutoff = Time.now.utc - (options[:older_than] * 24 * 60 * 60)
100
+ db.list_tasks(status: "closed", include_tombstones: true)
101
+ .select { |i| i.closed_at && i.closed_at < cutoff }
102
+ end
103
+
104
+ def print_cleanup_dry_run(candidates)
105
+ puts "Would delete #{candidates.size} task(s):"
106
+ candidates.each { |i| puts " #{i.id}: #{i.title}" }
107
+ end
108
+
109
+ def confirm_cleanup?(count)
110
+ puts "About to delete #{count} task(s). Continue? (y/n)"
111
+ $stdin.gets.strip.downcase == "y"
112
+ end
113
+
114
+ def delete_candidates(db, candidates)
115
+ candidates.each do |task|
116
+ db.child_tasks(task.id).each { |c| db.delete_task(c.id) } if options[:cascade]
117
+ db.delete_task(task.id)
118
+ end
119
+ end
120
+
121
+ def compaction_stats(db)
122
+ {
123
+ total_tasks: db.all_task_ids.size,
124
+ closed_tasks: db.list_tasks(status: "closed", include_tombstones: true).size,
125
+ ephemeral: db.find_ephemeral_workflows.size,
126
+ plans: db.find_plans.size,
127
+ workflows: db.find_workflows.size
128
+ }
129
+ end
130
+
131
+ # Marks closed tasks older than 30 days as tombstones.
132
+ def tombstone_old_closed_tasks(db)
133
+ cutoff = Time.now.utc - (30 * 24 * 60 * 60)
134
+ db.list_tasks(status: "closed").each do |task|
135
+ next unless task.closed_at && task.closed_at < cutoff
136
+
137
+ task.status = "tombstone"
138
+ db.update_task(task)
139
+ end
140
+ end
141
+
124
142
  # Delegate helper methods to parent CLI
125
- def with_database(&block) = CLI.new.with_database(&block)
143
+ def with_database(&) = CLI.new.with_database(&)
126
144
 
127
- def output(json_data, &human_block)
145
+ def output(json_data)
128
146
  if options[:json]
129
147
  puts Oj.dump(json_data, mode: :compat, indent: 2)
130
148
  else
131
- human_block.call
149
+ yield
132
150
  end
133
151
  end
134
152
  end
@@ -103,7 +103,7 @@ module TrakFlow
103
103
 
104
104
  # Load existing config or create new
105
105
  config_data = if File.exist?(config_path)
106
- YAML.safe_load(File.read(config_path), permitted_classes: [Symbol], symbolize_names: true) || {}
106
+ YAML.safe_load_file(config_path, permitted_classes: [Symbol], symbolize_names: true) || {}
107
107
  else
108
108
  {}
109
109
  end
@@ -161,20 +161,18 @@ module TrakFlow
161
161
  @pastel ||= Pastel.new
162
162
  end
163
163
 
164
- def output(json_data, &human_block)
164
+ def output(json_data)
165
165
  if options[:json]
166
166
  puts Oj.dump(json_data, mode: :compat, indent: 2)
167
167
  else
168
- human_block.call
168
+ yield
169
169
  end
170
170
  end
171
171
 
172
172
  def determine_config_path(global)
173
- if global
174
- XDG_CONFIG_PATH
175
- elsif File.exist?(PROJECT_CONFIG_PATH)
176
- File.expand_path(PROJECT_CONFIG_PATH)
177
- elsif File.directory?(".trak_flow")
173
+ return XDG_CONFIG_PATH if global
174
+
175
+ if File.exist?(PROJECT_CONFIG_PATH) || File.directory?(".trak_flow")
178
176
  File.expand_path(PROJECT_CONFIG_PATH)
179
177
  else
180
178
  XDG_CONFIG_PATH
@@ -235,12 +233,9 @@ module TrakFlow
235
233
  when "false" then false
236
234
  when "nil", "null" then nil
237
235
  else
238
- # Try integer
239
- if value.match?(/\A-?\d+\z/)
240
- value.to_i
241
- # Try float
242
- elsif value.match?(/\A-?\d+\.\d+\z/)
243
- value.to_f
236
+ case value
237
+ when /\A-?\d+\z/ then value.to_i
238
+ when /\A-?\d+\.\d+\z/ then value.to_f
244
239
  else
245
240
  # Keep as string, expand ~ for paths
246
241
  value.start_with?("~") ? File.expand_path(value) : value
@@ -56,14 +56,14 @@ module TrakFlow
56
56
  end
57
57
 
58
58
  # Delegate helper methods to parent CLI
59
- def with_database(&block) = CLI.new.with_database(&block)
59
+ def with_database(&) = CLI.new.with_database(&)
60
60
  def status_icon(status) = CLI.new.status_icon(status)
61
61
 
62
- def output(json_data, &human_block)
62
+ def output(json_data)
63
63
  if options[:json]
64
64
  puts Oj.dump(json_data, mode: :compat, indent: 2)
65
65
  else
66
- human_block.call
66
+ yield
67
67
  end
68
68
  end
69
69
  end
@@ -62,13 +62,13 @@ module TrakFlow
62
62
  private
63
63
 
64
64
  # Delegate helper methods to parent CLI
65
- def with_database(&block) = CLI.new.with_database(&block)
65
+ def with_database(&) = CLI.new.with_database(&)
66
66
 
67
- def output(json_data, &human_block)
67
+ def output(json_data)
68
68
  if options[:json]
69
69
  puts Oj.dump(json_data, mode: :compat, indent: 2)
70
70
  else
71
- human_block.call
71
+ yield
72
72
  end
73
73
  end
74
74
  end