Module: Brainiac::Plugins::Basecamp::Epic
- Defined in:
- lib/brainiac/plugins/basecamp/epic.rb
Overview
Parses Basecamp todolist-based epics.
Epic structure (Option C):
Todolist: "Epic: Build Auth System"
Todo: "#1234 — Set up auth models"
Description: <a href="https://app.fizzy.do/org/cards/1234">Fizzy #1234</a>
[depends:none] or [depends:1234,1235]
Todo: "#1235 — Add API endpoints"
Description: ...
Each todo in the list = one work item linked to a Fizzy card. Dependencies are declared in the todo description or title.
Defined Under Namespace
Classes: Task
Class Method Summary collapse
-
.build_todo_description(fizzy_card:, fizzy_account_id:, depends_on: [], agent: nil) ⇒ String
Generate a rich text HTML description for a todo linked to a Fizzy card.
-
.dependency_graph(tasks) ⇒ Hash
Build a full dependency graph from tasks.
-
.extract_deploy_env(title, description = nil) ⇒ String?
Extract deploy environment from epic title or description.
-
.parse_todos(todos) ⇒ Array<Task>
Parse todos from a todolist into structured tasks with dependency graph.
-
.unblocked_tasks(tasks) ⇒ Array<Task>
Determine which tasks are unblocked (all dependencies satisfied).
Class Method Details
.build_todo_description(fizzy_card:, fizzy_account_id:, depends_on: [], agent: nil) ⇒ String
Generate a rich text HTML description for a todo linked to a Fizzy card.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/brainiac/plugins/basecamp/epic.rb', line 109 def build_todo_description(fizzy_card:, fizzy_account_id:, depends_on: [], agent: nil) lines = [] lines << "<div>" lines << "<strong>Fizzy:</strong> <a href=\"https://app.fizzy.do/#{fizzy_account_id}/cards/#{fizzy_card}\">##{fizzy_card}</a><br>" if depends_on.any? dep_links = depends_on.map { |d| "<a href=\"https://app.fizzy.do/#{fizzy_account_id}/cards/#{d}\">##{d}</a>" } lines << "<strong>Depends on:</strong> #{dep_links.join(', ')}<br>" else lines << "<strong>Depends on:</strong> none<br>" end lines << "<strong>Agent:</strong> #{agent}<br>" if agent lines << "</div>" lines.join("\n") end |
.dependency_graph(tasks) ⇒ Hash
Build a full dependency graph from tasks.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/brainiac/plugins/basecamp/epic.rb', line 75 def dependency_graph(tasks) completed_cards = tasks.select { |t| t.status == :complete }.map(&:fizzy_card).compact { total: tasks.size, complete: tasks.count { |t| t.status == :complete }, pending: tasks.count { |t| t.status == :pending }, in_flight: tasks.count { |t| t.status == :in_flight }, blocked: tasks.count do |t| t.status == :pending && t.depends_on.any? { |dep| !completed_cards.include?(dep) } end, unblocked: unblocked_tasks(tasks).size, tasks: tasks.map do |t| { todo_id: t.todo_id, fizzy_card: t.fizzy_card, title: t.title, status: t.status, depends_on: t.depends_on, assignees: t.assignees, due_on: t.due_on } end } end |
.extract_deploy_env(title, description = nil) ⇒ String?
Extract deploy environment from epic title or description. Supports:
[deploy:dev02] — in title (preferred)
deploy:dev02 — in description (fallback)
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/brainiac/plugins/basecamp/epic.rb', line 134 def extract_deploy_env(title, description = nil) # Try [deploy:env] format in title (preferred) if title marker = "[deploy:" value_start = title.index(marker) if value_start value_start += marker.length value_end = title.index("]", value_start) if value_end environment = title[value_start...value_end].strip return environment unless environment.empty? end end end # Fallback: try deploy:env in description if description && (match = description.match(/deploy:(\S+)/i)) return match[1].strip end nil end |
.parse_todos(todos) ⇒ Array<Task>
Parse todos from a todolist into structured tasks with dependency graph.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/brainiac/plugins/basecamp/epic.rb', line 30 def parse_todos(todos) todos.map do |todo| title = todo["title"] || todo["content"] || "" description = todo["description"] || "" todo_id = todo["id"] completed = todo["completed"] || false assignees = (todo["assignees"] || []).map { |a| a["name"] || a["id"].to_s } due_on = todo["due_on"] fizzy_card = extract_fizzy_card(title) || extract_fizzy_card_from_description(description) depends_on = extract_dependencies(title) depends_on = extract_dependencies(description) if depends_on.empty? Task.new( todo_id: todo_id, title: title, fizzy_card: fizzy_card, depends_on: depends_on, status: completed ? :complete : :pending, completed: completed, description: description, assignees: assignees, due_on: due_on ) end end |
.unblocked_tasks(tasks) ⇒ Array<Task>
Determine which tasks are unblocked (all dependencies satisfied).
61 62 63 64 65 66 67 68 69 |
# File 'lib/brainiac/plugins/basecamp/epic.rb', line 61 def unblocked_tasks(tasks) completed_cards = tasks.select { |t| t.status == :complete }.map(&:fizzy_card).compact tasks.select do |task| task.status == :pending && task.fizzy_card && task.depends_on.all? { |dep| completed_cards.include?(dep) } end end |