trak_flow 0.1.3

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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/CHANGELOG.md +69 -0
  4. data/COMMITS.md +196 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +281 -0
  7. data/README.md +479 -0
  8. data/Rakefile +16 -0
  9. data/bin/tf +6 -0
  10. data/bin/tf_mcp +81 -0
  11. data/docs/.keep +0 -0
  12. data/docs/api/database.md +434 -0
  13. data/docs/api/ruby-library.md +349 -0
  14. data/docs/api/task-model.md +341 -0
  15. data/docs/assets/stylesheets/extra.css +53 -0
  16. data/docs/assets/trak_flow.jpg +0 -0
  17. data/docs/cli/admin-commands.md +369 -0
  18. data/docs/cli/dependency-commands.md +321 -0
  19. data/docs/cli/label-commands.md +222 -0
  20. data/docs/cli/overview.md +163 -0
  21. data/docs/cli/plan-commands.md +344 -0
  22. data/docs/cli/task-commands.md +333 -0
  23. data/docs/core-concepts/dependencies.md +232 -0
  24. data/docs/core-concepts/labels.md +217 -0
  25. data/docs/core-concepts/overview.md +178 -0
  26. data/docs/core-concepts/plans-workflows.md +264 -0
  27. data/docs/core-concepts/tasks.md +205 -0
  28. data/docs/getting-started/configuration.md +120 -0
  29. data/docs/getting-started/installation.md +79 -0
  30. data/docs/getting-started/quick-start.md +245 -0
  31. data/docs/index.md +169 -0
  32. data/docs/mcp/integration.md +302 -0
  33. data/docs/mcp/overview.md +206 -0
  34. data/docs/mcp/resources.md +284 -0
  35. data/docs/mcp/tools.md +457 -0
  36. data/examples/basic_usage.rb +365 -0
  37. data/examples/cli_demo.sh +314 -0
  38. data/examples/mcp/Gemfile +9 -0
  39. data/examples/mcp/Gemfile.lock +226 -0
  40. data/examples/mcp/http_demo.rb +232 -0
  41. data/examples/mcp/stdio_demo.rb +146 -0
  42. data/lib/trak_flow/cli/admin_commands.rb +136 -0
  43. data/lib/trak_flow/cli/config_commands.rb +260 -0
  44. data/lib/trak_flow/cli/dep_commands.rb +71 -0
  45. data/lib/trak_flow/cli/label_commands.rb +76 -0
  46. data/lib/trak_flow/cli/main_commands.rb +386 -0
  47. data/lib/trak_flow/cli/plan_commands.rb +185 -0
  48. data/lib/trak_flow/cli/workflow_commands.rb +133 -0
  49. data/lib/trak_flow/cli.rb +110 -0
  50. data/lib/trak_flow/config/defaults.yml +114 -0
  51. data/lib/trak_flow/config/section.rb +74 -0
  52. data/lib/trak_flow/config.rb +276 -0
  53. data/lib/trak_flow/graph/dependency_graph.rb +288 -0
  54. data/lib/trak_flow/id_generator.rb +52 -0
  55. data/lib/trak_flow/mcp/resources/base_resource.rb +25 -0
  56. data/lib/trak_flow/mcp/resources/dependency_graph.rb +31 -0
  57. data/lib/trak_flow/mcp/resources/label_list.rb +21 -0
  58. data/lib/trak_flow/mcp/resources/plan_by_id.rb +27 -0
  59. data/lib/trak_flow/mcp/resources/plan_list.rb +21 -0
  60. data/lib/trak_flow/mcp/resources/task_by_id.rb +31 -0
  61. data/lib/trak_flow/mcp/resources/task_list.rb +21 -0
  62. data/lib/trak_flow/mcp/resources/task_next.rb +30 -0
  63. data/lib/trak_flow/mcp/resources/workflow_by_id.rb +27 -0
  64. data/lib/trak_flow/mcp/resources/workflow_list.rb +21 -0
  65. data/lib/trak_flow/mcp/server.rb +140 -0
  66. data/lib/trak_flow/mcp/tools/base_tool.rb +29 -0
  67. data/lib/trak_flow/mcp/tools/comment_add.rb +33 -0
  68. data/lib/trak_flow/mcp/tools/dep_add.rb +34 -0
  69. data/lib/trak_flow/mcp/tools/dep_remove.rb +25 -0
  70. data/lib/trak_flow/mcp/tools/label_add.rb +28 -0
  71. data/lib/trak_flow/mcp/tools/label_remove.rb +25 -0
  72. data/lib/trak_flow/mcp/tools/plan_add_step.rb +35 -0
  73. data/lib/trak_flow/mcp/tools/plan_create.rb +33 -0
  74. data/lib/trak_flow/mcp/tools/plan_run.rb +58 -0
  75. data/lib/trak_flow/mcp/tools/plan_start.rb +58 -0
  76. data/lib/trak_flow/mcp/tools/task_block.rb +27 -0
  77. data/lib/trak_flow/mcp/tools/task_close.rb +26 -0
  78. data/lib/trak_flow/mcp/tools/task_create.rb +51 -0
  79. data/lib/trak_flow/mcp/tools/task_defer.rb +27 -0
  80. data/lib/trak_flow/mcp/tools/task_start.rb +25 -0
  81. data/lib/trak_flow/mcp/tools/task_update.rb +36 -0
  82. data/lib/trak_flow/mcp/tools/workflow_discard.rb +28 -0
  83. data/lib/trak_flow/mcp/tools/workflow_summarize.rb +34 -0
  84. data/lib/trak_flow/mcp.rb +38 -0
  85. data/lib/trak_flow/models/comment.rb +71 -0
  86. data/lib/trak_flow/models/dependency.rb +96 -0
  87. data/lib/trak_flow/models/label.rb +90 -0
  88. data/lib/trak_flow/models/task.rb +188 -0
  89. data/lib/trak_flow/storage/database.rb +638 -0
  90. data/lib/trak_flow/storage/jsonl.rb +259 -0
  91. data/lib/trak_flow/time_parser.rb +15 -0
  92. data/lib/trak_flow/version.rb +5 -0
  93. data/lib/trak_flow.rb +100 -0
  94. data/mkdocs.yml +143 -0
  95. metadata +392 -0
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fast_mcp"
4
+ require "puma"
5
+ require "puma/configuration"
6
+ require "rack"
7
+ require "rackup"
8
+
9
+ module TrakFlow
10
+ module Mcp
11
+ class Server
12
+ attr_reader :name, :version, :mcp_server
13
+
14
+ def initialize(name: "trak_flow", version: TrakFlow::VERSION)
15
+ @name = name
16
+ @version = version
17
+ @mcp_server = FastMcp::Server.new(name: name, version: version)
18
+ register_tools
19
+ register_resources
20
+ end
21
+
22
+ def start_stdio
23
+ # Use warn (stderr) not puts (stdout) - stdout is for MCP protocol
24
+ warn "Starting TrakFlow MCP Server (stdio transport)..."
25
+ mcp_server.start
26
+ end
27
+
28
+ def start_http(port: nil)
29
+ port ||= TrakFlow.config.mcp.port
30
+ puts "Starting TrakFlow MCP Server (HTTP transport on port #{port})..."
31
+
32
+ rack_app = create_rack_app
33
+
34
+ # Configure Puma (supports rack.hijack for SSE)
35
+ puma_config = Puma::Configuration.new do |config|
36
+ config.bind "tcp://0.0.0.0:#{port}"
37
+ config.threads 1, 5
38
+ config.workers 0
39
+ config.quiet
40
+ config.app rack_app
41
+ end
42
+
43
+ launcher = Puma::Launcher.new(puma_config)
44
+
45
+ trap("INT") { launcher.stop }
46
+ trap("TERM") { launcher.stop }
47
+
48
+ launcher.run
49
+ end
50
+
51
+ def start_both(http_port: nil)
52
+ http_port ||= TrakFlow.config.mcp.port
53
+ puts "Starting TrakFlow MCP Server (dual transport)..."
54
+ puts " - HTTP: port #{http_port}"
55
+ puts " - STDIO: reading from stdin"
56
+
57
+ # Start HTTP in a thread
58
+ http_thread = Thread.new do
59
+ start_http_server(http_port)
60
+ end
61
+
62
+ # Run STDIO in main thread (blocking)
63
+ mcp_server.start
64
+
65
+ http_thread.join
66
+ end
67
+
68
+ private
69
+
70
+ def create_rack_app
71
+ server = mcp_server
72
+ Rack::Builder.new do
73
+ use FastMcp::Transports::RackTransport, server
74
+ run ->(_env) { [404, { "Content-Type" => "text/plain" }, ["TrakFlow MCP Server - Use /mcp/sse for SSE transport"]] }
75
+ end.to_app
76
+ end
77
+
78
+ def start_http_server(port)
79
+ rack_app = create_rack_app
80
+
81
+ # Configure Puma (supports rack.hijack for SSE)
82
+ puma_config = Puma::Configuration.new do |config|
83
+ config.bind "tcp://0.0.0.0:#{port}"
84
+ config.threads 1, 5
85
+ config.workers 0
86
+ config.quiet
87
+ config.app rack_app
88
+ end
89
+
90
+ launcher = Puma::Launcher.new(puma_config)
91
+
92
+ trap("INT") { launcher.stop }
93
+ trap("TERM") { launcher.stop }
94
+
95
+ launcher.run
96
+ end
97
+
98
+ def register_tools
99
+ # Task management tools
100
+ mcp_server.register_tool(Tools::TaskCreate)
101
+ mcp_server.register_tool(Tools::TaskUpdate)
102
+ mcp_server.register_tool(Tools::TaskClose)
103
+ mcp_server.register_tool(Tools::TaskStart)
104
+ mcp_server.register_tool(Tools::TaskBlock)
105
+ mcp_server.register_tool(Tools::TaskDefer)
106
+
107
+ # Plan/Workflow tools
108
+ mcp_server.register_tool(Tools::PlanCreate)
109
+ mcp_server.register_tool(Tools::PlanAddStep)
110
+ mcp_server.register_tool(Tools::PlanStart)
111
+ mcp_server.register_tool(Tools::PlanRun)
112
+ mcp_server.register_tool(Tools::WorkflowDiscard)
113
+ mcp_server.register_tool(Tools::WorkflowSummarize)
114
+
115
+ # Dependency tools
116
+ mcp_server.register_tool(Tools::DepAdd)
117
+ mcp_server.register_tool(Tools::DepRemove)
118
+
119
+ # Label tools
120
+ mcp_server.register_tool(Tools::LabelAdd)
121
+ mcp_server.register_tool(Tools::LabelRemove)
122
+
123
+ # Comment tool
124
+ mcp_server.register_tool(Tools::CommentAdd)
125
+ end
126
+
127
+ def register_resources
128
+ mcp_server.register_resource(Resources::TaskList)
129
+ mcp_server.register_resource(Resources::TaskById)
130
+ mcp_server.register_resource(Resources::TaskNext)
131
+ mcp_server.register_resource(Resources::PlanList)
132
+ mcp_server.register_resource(Resources::PlanById)
133
+ mcp_server.register_resource(Resources::WorkflowList)
134
+ mcp_server.register_resource(Resources::WorkflowById)
135
+ mcp_server.register_resource(Resources::LabelList)
136
+ mcp_server.register_resource(Resources::DependencyGraph)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class BaseTool < FastMcp::Tool
7
+ class << self
8
+ def with_database
9
+ TrakFlow.ensure_initialized!
10
+
11
+ db = Storage::Database.new
12
+ db.connect
13
+
14
+ jsonl = Storage::Jsonl.new
15
+ jsonl.import(db) if jsonl.exists?
16
+
17
+ result = yield db
18
+
19
+ jsonl.export(db) if db.dirty?
20
+
21
+ result
22
+ ensure
23
+ db&.close
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class CommentAdd < BaseTool
7
+ tool_name "comment_add"
8
+ description "Add a comment to a task"
9
+
10
+ arguments do
11
+ required(:task_id).filled(:string).description("Task ID")
12
+ required(:body).filled(:string).description("Comment text")
13
+ optional(:author).filled(:string).description("Comment author (default: robot)")
14
+ end
15
+
16
+ def call(task_id:, body:, author: "robot")
17
+ self.class.with_database do |db|
18
+ db.find_task!(task_id)
19
+
20
+ comment = Models::Comment.new(
21
+ task_id: task_id,
22
+ body: body,
23
+ author: author
24
+ )
25
+ db.add_comment(comment)
26
+
27
+ comment.to_h
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class DepAdd < BaseTool
7
+ tool_name "dep_add"
8
+ description "Add a dependency between two tasks"
9
+
10
+ arguments do
11
+ required(:source_id).filled(:string).description("Source task ID")
12
+ required(:target_id).filled(:string).description("Target task ID")
13
+ optional(:type).filled(:string).description("Dependency type: blocks, related, parent-child, discovered-from (default: blocks)")
14
+ end
15
+
16
+ def call(source_id:, target_id:, type: "blocks")
17
+ self.class.with_database do |db|
18
+ db.find_task!(source_id)
19
+ db.find_task!(target_id)
20
+
21
+ dep = Models::Dependency.new(
22
+ source_id: source_id,
23
+ target_id: target_id,
24
+ type: type
25
+ )
26
+ db.add_dependency(dep)
27
+
28
+ { source_id: source_id, target_id: target_id, type: type, success: true }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class DepRemove < BaseTool
7
+ tool_name "dep_remove"
8
+ description "Remove a dependency between two tasks"
9
+
10
+ arguments do
11
+ required(:source_id).filled(:string).description("Source task ID")
12
+ required(:target_id).filled(:string).description("Target task ID")
13
+ end
14
+
15
+ def call(source_id:, target_id:)
16
+ self.class.with_database do |db|
17
+ db.remove_dependency(source_id, target_id)
18
+
19
+ { source_id: source_id, target_id: target_id, removed: true }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class LabelAdd < BaseTool
7
+ tool_name "label_add"
8
+ description "Add a label to a task"
9
+
10
+ arguments do
11
+ required(:task_id).filled(:string).description("Task ID")
12
+ required(:name).filled(:string).description("Label name")
13
+ end
14
+
15
+ def call(task_id:, name:)
16
+ self.class.with_database do |db|
17
+ db.find_task!(task_id)
18
+
19
+ label = Models::Label.new(task_id: task_id, name: name)
20
+ db.add_label(label)
21
+
22
+ { task_id: task_id, label: name, success: true }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class LabelRemove < BaseTool
7
+ tool_name "label_remove"
8
+ description "Remove a label from a task"
9
+
10
+ arguments do
11
+ required(:task_id).filled(:string).description("Task ID")
12
+ required(:name).filled(:string).description("Label name")
13
+ end
14
+
15
+ def call(task_id:, name:)
16
+ self.class.with_database do |db|
17
+ db.remove_label(task_id, name)
18
+
19
+ { task_id: task_id, label: name, removed: true }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class PlanAddStep < BaseTool
7
+ tool_name "plan_add_step"
8
+ description "Add a step (child task) to a Plan"
9
+
10
+ arguments do
11
+ required(:plan_id).filled(:string).description("Plan ID to add step to")
12
+ required(:title).filled(:string).description("Step title")
13
+ optional(:description).filled(:string).description("Step description")
14
+ optional(:type).filled(:string).description("Task type (default: task)")
15
+ optional(:priority).filled(:integer).description("Priority 0-4 (default: 2)")
16
+ end
17
+
18
+ def call(plan_id:, title:, description: nil, type: "task", priority: 2)
19
+ self.class.with_database do |db|
20
+ plan = db.find_task!(plan_id)
21
+ raise TrakFlow::Error, "#{plan_id} is not a Plan" unless plan.plan?
22
+
23
+ task = db.create_child_task(plan_id, {
24
+ title: title,
25
+ description: description || "",
26
+ type: type,
27
+ priority: priority
28
+ })
29
+ task.to_h
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class PlanCreate < BaseTool
7
+ tool_name "plan_create"
8
+ description "Create a new Plan (workflow blueprint)"
9
+
10
+ arguments do
11
+ required(:title).filled(:string).description("Plan title")
12
+ optional(:description).filled(:string).description("Plan description")
13
+ optional(:type).filled(:string).description("Task type (default: task)")
14
+ optional(:priority).filled(:integer).description("Priority 0-4 (default: 2)")
15
+ end
16
+
17
+ def call(title:, description: nil, type: "task", priority: 2)
18
+ self.class.with_database do |db|
19
+ plan = Models::Task.new(
20
+ title: title,
21
+ description: description || "",
22
+ type: type,
23
+ priority: priority,
24
+ plan: true
25
+ )
26
+ db.create_task(plan)
27
+ plan.to_h
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class PlanRun < BaseTool
7
+ tool_name "plan_run"
8
+ description "Create an ephemeral Workflow from a Plan (one-shot, garbage collectible)"
9
+
10
+ arguments do
11
+ required(:plan_id).filled(:string).description("Plan ID to instantiate")
12
+ optional(:variables).hash.description("Template variables for interpolation")
13
+ end
14
+
15
+ def call(plan_id:, variables: {})
16
+ self.class.with_database do |db|
17
+ plan = db.find_task!(plan_id)
18
+ raise TrakFlow::Error, "#{plan_id} is not a Plan" unless plan.plan?
19
+
20
+ workflow = Models::Task.new(
21
+ title: interpolate_vars(plan.title, variables),
22
+ description: interpolate_vars(plan.description, variables),
23
+ type: plan.type,
24
+ priority: plan.priority,
25
+ source_plan_id: plan.id,
26
+ ephemeral: true
27
+ )
28
+ db.create_task(workflow)
29
+ workflow.append_trace("INSTANTIATED", "from Plan #{plan.id} (ephemeral)")
30
+ db.update_task(workflow)
31
+
32
+ db.find_plan_tasks(plan_id).each do |step|
33
+ db.create_child_task(workflow.id, {
34
+ title: interpolate_vars(step.title, variables),
35
+ description: interpolate_vars(step.description, variables),
36
+ type: step.type,
37
+ priority: step.priority,
38
+ ephemeral: true
39
+ })
40
+ end
41
+
42
+ workflow.to_h
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def interpolate_vars(text, vars)
49
+ return text if text.nil? || vars.empty?
50
+
51
+ result = text.dup
52
+ vars.each { |key, value| result.gsub!("{{#{key}}}", value.to_s) }
53
+ result
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class PlanStart < BaseTool
7
+ tool_name "plan_start"
8
+ description "Create a persistent Workflow from a Plan"
9
+
10
+ arguments do
11
+ required(:plan_id).filled(:string).description("Plan ID to instantiate")
12
+ optional(:variables).hash.description("Template variables for interpolation")
13
+ end
14
+
15
+ def call(plan_id:, variables: {})
16
+ self.class.with_database do |db|
17
+ plan = db.find_task!(plan_id)
18
+ raise TrakFlow::Error, "#{plan_id} is not a Plan" unless plan.plan?
19
+
20
+ workflow = Models::Task.new(
21
+ title: interpolate_vars(plan.title, variables),
22
+ description: interpolate_vars(plan.description, variables),
23
+ type: plan.type,
24
+ priority: plan.priority,
25
+ source_plan_id: plan.id,
26
+ ephemeral: false
27
+ )
28
+ db.create_task(workflow)
29
+ workflow.append_trace("INSTANTIATED", "from Plan #{plan.id}")
30
+ db.update_task(workflow)
31
+
32
+ db.find_plan_tasks(plan_id).each do |step|
33
+ db.create_child_task(workflow.id, {
34
+ title: interpolate_vars(step.title, variables),
35
+ description: interpolate_vars(step.description, variables),
36
+ type: step.type,
37
+ priority: step.priority,
38
+ ephemeral: false
39
+ })
40
+ end
41
+
42
+ workflow.to_h
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def interpolate_vars(text, vars)
49
+ return text if text.nil? || vars.empty?
50
+
51
+ result = text.dup
52
+ vars.each { |key, value| result.gsub!("{{#{key}}}", value.to_s) }
53
+ result
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class TaskBlock < BaseTool
7
+ tool_name "task_block"
8
+ description "Mark a task as blocked"
9
+
10
+ arguments do
11
+ required(:id).filled(:string).description("Task ID to block")
12
+ optional(:reason).filled(:string).description("Reason for blocking")
13
+ end
14
+
15
+ def call(id:, reason: nil)
16
+ self.class.with_database do |db|
17
+ task = db.find_task!(id)
18
+ task.status = "blocked"
19
+ task.append_trace("BLOCKED", reason) if reason
20
+ db.update_task(task)
21
+ task.to_h
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class TaskClose < BaseTool
7
+ tool_name "task_close"
8
+ description "Close a task"
9
+
10
+ arguments do
11
+ required(:id).filled(:string).description("Task ID to close")
12
+ optional(:reason).filled(:string).description("Reason for closing")
13
+ end
14
+
15
+ def call(id:, reason: nil)
16
+ self.class.with_database do |db|
17
+ task = db.find_task!(id)
18
+ task.close!(reason: reason)
19
+ db.update_task(task)
20
+ task.to_h
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class TaskCreate < BaseTool
7
+ tool_name "task_create"
8
+ description "Create a new task with title, type, and priority"
9
+
10
+ arguments do
11
+ required(:title).filled(:string).description("Task title")
12
+ optional(:type).filled(:string).description("Task type: bug, feature, task, epic, chore (default: task)")
13
+ optional(:priority).filled(:integer).description("Priority 0-4: 0=critical, 4=backlog (default: 2)")
14
+ optional(:description).filled(:string).description("Task description")
15
+ optional(:assignee).filled(:string).description("Assignee name")
16
+ optional(:parent_id).filled(:string).description("Parent task ID for creating child tasks")
17
+ optional(:labels).array(:string).description("Labels to add")
18
+ end
19
+
20
+ def call(title:, type: "task", priority: 2, description: nil, assignee: nil, parent_id: nil, labels: [])
21
+ self.class.with_database do |db|
22
+ task = if parent_id
23
+ db.create_child_task(parent_id, {
24
+ title: title,
25
+ description: description,
26
+ type: type,
27
+ priority: priority,
28
+ assignee: assignee
29
+ })
30
+ else
31
+ new_task = Models::Task.new(
32
+ title: title,
33
+ description: description,
34
+ type: type,
35
+ priority: priority,
36
+ assignee: assignee
37
+ )
38
+ db.create_task(new_task)
39
+ end
40
+
41
+ labels.each do |label_name|
42
+ db.add_label(Models::Label.new(task_id: task.id, name: label_name))
43
+ end
44
+
45
+ task.to_h
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class TaskDefer < BaseTool
7
+ tool_name "task_defer"
8
+ description "Defer a task for later"
9
+
10
+ arguments do
11
+ required(:id).filled(:string).description("Task ID to defer")
12
+ optional(:reason).filled(:string).description("Reason for deferring")
13
+ end
14
+
15
+ def call(id:, reason: nil)
16
+ self.class.with_database do |db|
17
+ task = db.find_task!(id)
18
+ task.status = "deferred"
19
+ task.append_trace("DEFERRED", reason) if reason
20
+ db.update_task(task)
21
+ task.to_h
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrakFlow
4
+ module Mcp
5
+ module Tools
6
+ class TaskStart < BaseTool
7
+ tool_name "task_start"
8
+ description "Start working on a task (set status to in_progress)"
9
+
10
+ arguments do
11
+ required(:id).filled(:string).description("Task ID to start")
12
+ end
13
+
14
+ def call(id:)
15
+ self.class.with_database do |db|
16
+ task = db.find_task!(id)
17
+ task.status = "in_progress"
18
+ db.update_task(task)
19
+ task.to_h
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end