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
@@ -18,12 +18,9 @@ module TrakFlow
18
18
  # - Plans are exported (persistent blueprints)
19
19
  # - Ephemeral Workflows are NOT exported (temporary only)
20
20
  def export(db)
21
- entities = []
22
-
23
21
  # Export regular tasks (excluding ephemeral) and include Plans
24
- db.list_tasks(include_ephemeral: false, include_plans: true, include_tombstones: true).each do |task|
25
- entities << { type: "task", data: task.to_h }
26
- end
22
+ entities = db.list_tasks(include_ephemeral: false, include_plans: true, include_tombstones: true)
23
+ .map { |task| { type: "task", data: task.to_h } }
27
24
 
28
25
  db.all_task_ids.each do |task_id|
29
26
  db.find_dependencies(task_id, direction: :outgoing).each do |dep|
@@ -51,39 +48,42 @@ module TrakFlow
51
48
  orphan_handling ||= TrakFlow.config.get("import.orphan_handling")
52
49
  error_policy ||= TrakFlow.config.get("import.error_policy") || "warn"
53
50
 
54
- entities = read_entities
55
- tasks = []
56
- dependencies = []
57
- labels = []
58
- comments = []
59
- import_errors = []
60
-
61
- entities.each do |entity|
62
- case entity[:type]
63
- when "task"
64
- tasks << Models::Task.from_hash(entity[:data])
65
- when "dependency"
66
- dependencies << Models::Dependency.from_hash(entity[:data])
67
- when "label"
68
- labels << Models::Label.from_hash(entity[:data])
69
- when "comment"
70
- comments << Models::Comment.from_hash(entity[:data])
71
- end
72
- end
73
-
74
- tasks = handle_orphans(tasks, orphan_handling)
51
+ models = build_import_models(read_entities)
52
+ tasks = handle_orphans(models[:task], orphan_handling)
75
53
 
76
54
  db.import_tasks(tasks)
77
55
 
78
- import_errors += import_entities(db, :add_dependency, dependencies, error_policy)
79
- import_errors += import_entities(db, :add_label, labels, error_policy)
80
- import_errors += import_entities(db, :add_comment, comments, error_policy)
56
+ import_errors = import_entities(db, :add_dependency, models[:dependency], error_policy)
57
+ import_errors += import_entities(db, :add_label, models[:label], error_policy)
58
+ import_errors += import_entities(db, :add_comment, models[:comment], error_policy)
81
59
 
82
60
  raise_if_strict_errors(import_errors, error_policy)
83
61
  end
84
62
 
85
63
  private
86
64
 
65
+ IMPORT_MODEL_CLASSES = {
66
+ "task" => Models::Task,
67
+ "dependency" => Models::Dependency,
68
+ "label" => Models::Label,
69
+ "comment" => Models::Comment
70
+ }.freeze
71
+
72
+ private_constant :IMPORT_MODEL_CLASSES
73
+
74
+ # Groups raw JSONL entities into model instances keyed by type;
75
+ # entities with an unknown type are dropped.
76
+ def build_import_models(entities)
77
+ models = { task: [], dependency: [], label: [], comment: [] }
78
+
79
+ entities.each do |entity|
80
+ klass = IMPORT_MODEL_CLASSES[entity[:type]]
81
+ models[entity[:type].to_sym] << klass.from_hash(entity[:data]) if klass
82
+ end
83
+
84
+ models
85
+ end
86
+
87
87
  def import_entities(db, method, entities, error_policy)
88
88
  errors = []
89
89
  entities.each do |entity|
@@ -174,44 +174,43 @@ module TrakFlow
174
174
  def incremental_export(db, changed_ids)
175
175
  return export(db) unless File.exist?(path)
176
176
 
177
- existing = read_entities
178
- existing_by_id = {}
177
+ entities_by_key = index_entities(read_entities)
178
+ changed_ids.each { |task_id| apply_task_change(db, task_id, entities_by_key) }
179
179
 
180
- existing.each do |entity|
180
+ write_entities(entities_by_key.values)
181
+ db.mark_clean!
182
+ end
183
+
184
+ private
185
+
186
+ # Indexes entities by "type-id" for incremental merging.
187
+ def index_entities(entities)
188
+ entities.each_with_object({}) do |entity, index|
181
189
  id = entity.dig(:data, :id)
182
- existing_by_id["#{entity[:type]}-#{id}"] = entity if id
190
+ index["#{entity[:type]}-#{id}"] = entity if id
183
191
  end
192
+ end
184
193
 
185
- changed_ids.each do |task_id|
186
- task = db.find_task(task_id)
187
- if task
188
- key = "task-#{task_id}"
189
- existing_by_id[key] = { type: "task", data: task.to_h }
190
-
191
- db.find_dependencies(task_id, direction: :outgoing).each do |dep|
192
- dep_key = "dependency-#{dep.id}"
193
- existing_by_id[dep_key] = { type: "dependency", data: dep.to_h }
194
- end
195
-
196
- db.find_labels(task_id).each do |label|
197
- label_key = "label-#{label.id}"
198
- existing_by_id[label_key] = { type: "label", data: label.to_h }
199
- end
200
-
201
- db.find_comments(task_id).each do |comment|
202
- comment_key = "comment-#{comment.id}"
203
- existing_by_id[comment_key] = { type: "comment", data: comment.to_h }
204
- end
205
- else
206
- existing_by_id.delete("task-#{task_id}")
207
- end
194
+ # Replaces one task's entities in the index — or removes the task's
195
+ # entry when it no longer exists in the database.
196
+ def apply_task_change(db, task_id, index)
197
+ task = db.find_task(task_id)
198
+ return index.delete("task-#{task_id}") unless task
199
+
200
+ index["task-#{task_id}"] = { type: "task", data: task.to_h }
201
+
202
+ db.find_dependencies(task_id, direction: :outgoing).each do |dep|
203
+ index["dependency-#{dep.id}"] = { type: "dependency", data: dep.to_h }
208
204
  end
209
205
 
210
- write_entities(existing_by_id.values)
211
- db.mark_clean!
212
- end
206
+ db.find_labels(task_id).each do |label|
207
+ index["label-#{label.id}"] = { type: "label", data: label.to_h }
208
+ end
213
209
 
214
- private
210
+ db.find_comments(task_id).each do |comment|
211
+ index["comment-#{comment.id}"] = { type: "comment", data: comment.to_h }
212
+ end
213
+ end
215
214
 
216
215
  def valid_entity?(data)
217
216
  return false unless data.is_a?(Hash)
@@ -237,8 +236,6 @@ module TrakFlow
237
236
  return valid if orphans.empty?
238
237
 
239
238
  case handling
240
- when "allow"
241
- valid + orphans
242
239
  when "skip"
243
240
  debug_me "Skipping #{orphans.size} orphaned tasks"
244
241
  valid
@@ -251,6 +248,7 @@ module TrakFlow
251
248
  when "strict"
252
249
  raise ValidationError, "Found #{orphans.size} orphaned tasks with missing parents"
253
250
  else
251
+ # "allow" and any unrecognized handling keep orphans as-is
254
252
  valid + orphans
255
253
  end
256
254
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TrakFlow
4
- VERSION = '0.1.3'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/trak_flow.rb CHANGED
@@ -4,7 +4,6 @@ require "digest"
4
4
  require "fileutils"
5
5
  require "json"
6
6
  require "securerandom"
7
- require "set"
8
7
  require "time"
9
8
 
10
9
  require "oj"
data/trak_flow.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/trak_flow/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'trak_flow'
7
+ spec.version = TrakFlow::VERSION
8
+ spec.authors = ['Dewayne VanHoozer']
9
+ spec.email = ['dewayne@vanhoozer.me']
10
+
11
+ spec.summary = 'A distributed task tracking system for Robots'
12
+ spec.description = <<~DESC
13
+ TrakFlow is a specialized task tracking system designed for robots.
14
+ It implements a dependency-aware graph, allowing
15
+ robots to handle complex/lengthy task workflows without losing context. Tasks are
16
+ stored as JSONL files within a designated directory, using Git as the
17
+ underlying database for versioning, branching, and merging.
18
+ DESC
19
+ spec.homepage = 'https://github.com/madbomber/trak_flow'
20
+ spec.license = 'MIT'
21
+ spec.required_ruby_version = '>= 3.1.0'
22
+
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) ||
26
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github])
27
+ end
28
+ end
29
+ spec.bindir = 'bin'
30
+ spec.executables = %w[tf tf_mcp]
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'anyway_config', '~> 2.0'
34
+ spec.add_dependency 'debug_me'
35
+ spec.add_dependency 'fast-mcp'
36
+ spec.add_dependency 'oj', '~> 3.16'
37
+ spec.add_dependency 'pastel', '~> 0.8'
38
+ spec.add_dependency 'sequel', '~> 5.0'
39
+ spec.add_dependency 'sqlite3', '~> 2.0'
40
+ spec.add_dependency 'thor', '~> 1.3'
41
+ spec.add_dependency 'tty-spinner', '~> 0.9'
42
+ spec.add_dependency 'tty-table', '~> 0.12'
43
+
44
+ # puma and rackup are deliberately absent: the MCP HTTP transport
45
+ # lazy-loads them (see TrakFlow::Mcp::Server#require_http_transport_gems)
46
+ # so consumers that only use the models/storage API or the stdio
47
+ # transport don't carry a web server. To run the HTTP transport, add
48
+ # `gem 'puma', '>= 7.2.1'` and `gem 'rackup'` to your Gemfile.
49
+
50
+ spec.add_development_dependency 'bundler'
51
+ spec.add_development_dependency 'minitest', '~> 5.0'
52
+ spec.add_development_dependency 'minitest-reporters', '~> 1.6'
53
+ spec.add_development_dependency 'rake', '~> 13.0'
54
+ spec.add_development_dependency 'rubocop', '~> 1.0'
55
+ spec.add_development_dependency 'simplecov', '~> 0.22'
56
+ spec.metadata['rubygems_mfa_required'] = 'true'
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trak_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -149,34 +149,6 @@ dependencies:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
151
  version: '0.12'
152
- - !ruby/object:Gem::Dependency
153
- name: puma
154
- requirement: !ruby/object:Gem::Requirement
155
- requirements:
156
- - - "~>"
157
- - !ruby/object:Gem::Version
158
- version: '6.0'
159
- type: :runtime
160
- prerelease: false
161
- version_requirements: !ruby/object:Gem::Requirement
162
- requirements:
163
- - - "~>"
164
- - !ruby/object:Gem::Version
165
- version: '6.0'
166
- - !ruby/object:Gem::Dependency
167
- name: rackup
168
- requirement: !ruby/object:Gem::Requirement
169
- requirements:
170
- - - "~>"
171
- - !ruby/object:Gem::Version
172
- version: '2.0'
173
- type: :runtime
174
- prerelease: false
175
- version_requirements: !ruby/object:Gem::Requirement
176
- requirements:
177
- - - "~>"
178
- - !ruby/object:Gem::Version
179
- version: '2.0'
180
152
  - !ruby/object:Gem::Dependency
181
153
  name: bundler
182
154
  requirement: !ruby/object:Gem::Requirement
@@ -276,12 +248,18 @@ extensions: []
276
248
  extra_rdoc_files: []
277
249
  files:
278
250
  - ".envrc"
251
+ - ".fasterer.yml"
252
+ - ".loki"
253
+ - ".reek.yml"
254
+ - ".rubocop.yml"
255
+ - Archspec.rb
279
256
  - CHANGELOG.md
280
257
  - COMMITS.md
281
258
  - Gemfile
282
259
  - Gemfile.lock
283
260
  - README.md
284
261
  - Rakefile
262
+ - _typos.toml
285
263
  - bin/tf
286
264
  - bin/tf_mcp
287
265
  - docs/.keep
@@ -368,10 +346,12 @@ files:
368
346
  - lib/trak_flow/time_parser.rb
369
347
  - lib/trak_flow/version.rb
370
348
  - mkdocs.yml
349
+ - trak_flow.gemspec
371
350
  homepage: https://github.com/madbomber/trak_flow
372
351
  licenses:
373
352
  - MIT
374
- metadata: {}
353
+ metadata:
354
+ rubygems_mfa_required: 'true'
375
355
  rdoc_options: []
376
356
  require_paths:
377
357
  - lib
@@ -386,7 +366,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
386
366
  - !ruby/object:Gem::Version
387
367
  version: '0'
388
368
  requirements: []
389
- rubygems_version: 4.0.3
369
+ rubygems_version: 4.0.20
390
370
  specification_version: 4
391
371
  summary: A distributed task tracking system for Robots
392
372
  test_files: []