Class: Agentilda::Linear::Issues
- Inherits:
-
Object
- Object
- Agentilda::Linear::Issues
- Defined in:
- lib/agentilda/linear/issue.rb
Overview
A plan's record of what it has put into Linear.
Without it, a second linear import cannot tell an issue it created
last week from one it has never created, and the only two outcomes are
duplicating everything or searching Linear by title on every run. The
file is the memory, and it is a committed artifact rather than a dotfile
so the record travels with the plan and shows up in review.
The Synced column is what makes an update cheap to decide: it is a
digest of the payload last pushed, so a run can tell — with no network
at all — whether anything about this issue has changed since. A digest
that no longer matches is the whole trigger for an update.
Constant Summary collapse
- FILENAME =
The file this reads and writes.
"linear.md"- PARENT =
The unit key given to the issue that stands for the whole folder. Its children carry their own unit keys; it carries this, so one table can hold a plan and its parts without a second shape to parse.
"PLAN"
Class Method Summary collapse
-
.digest(payload) ⇒ String
Fingerprint of a payload, short enough to read in a table.
- .escape(text) ⇒ String
- .heading(team, project) ⇒ String
- .link(text, url) ⇒ String
-
.order(issues) ⇒ Array<Agentilda::Linear::Issue>
The folder's own issue first, then its children.
- .render(team:, project:, issues:) ⇒ String
Instance Method Summary collapse
-
#all ⇒ Array<Agentilda::Linear::Issue>
Possibly empty.
-
#by_unit ⇒ Hash{String => Agentilda::Linear::Issue}
Keyed by unit.
- #exist? ⇒ Boolean
-
#initialize(dir:) ⇒ Issues
constructor
A new instance of Issues.
-
#parent ⇒ Agentilda::Linear::Issue?
The issue standing for the folder itself.
-
#path ⇒ String
Absolute path to
linear.md. -
#project ⇒ Hash?
The project this plan was last filed under, if any.
-
#write(team:, project:, issues:) ⇒ String
The path written.
Constructor Details
#initialize(dir:) ⇒ Issues
Returns a new instance of Issues.
103 104 105 |
# File 'lib/agentilda/linear/issue.rb', line 103 def initialize(dir:) @dir = dir end |
Class Method Details
.digest(payload) ⇒ String
Fingerprint of a payload, short enough to read in a table.
48 |
# File 'lib/agentilda/linear/issue.rb', line 48 def self.digest(payload) = ::Digest::SHA256.hexdigest(JSON.generate(payload))[0, 8] |
.escape(text) ⇒ String
100 |
# File 'lib/agentilda/linear/issue.rb', line 100 def self.escape(text) = text.to_s.gsub("|", "\\|").gsub(/\s+/, " ").strip |
.heading(team, project) ⇒ String
87 88 89 90 91 |
# File 'lib/agentilda/linear/issue.rb', line 87 def self.heading(team, project) return "Team **#{team}**." unless project "Team **#{team}** · project #{link(project[:name], project[:url])}" end |
.link(text, url) ⇒ String
96 |
# File 'lib/agentilda/linear/issue.rb', line 96 def self.link(text, url) = url ? "[#{escape(text)}](#{url})" : escape(text) |
.order(issues) ⇒ Array<Agentilda::Linear::Issue>
The folder's own issue first, then its children. A table that opens with a child reads as a list of unrelated issues.
79 80 81 82 |
# File 'lib/agentilda/linear/issue.rb', line 79 def self.order(issues) parent, children = issues.partition { |i| i.unit == PARENT } parent + children end |
.render(team:, project:, issues:) ⇒ String
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/agentilda/linear/issue.rb', line 54 def self.render(team:, project:, issues:) rows = order(issues).map { |i| "| #{i.unit} | #{link(i.identifier, i.url)} | #{escape(i.title)} | #{i.state} | #{i.digest} |" } <<~MARKDOWN # Linear #{heading(team, project)} | Unit | Issue | Title | State | Synced | | :--- | :---- | :---- | ----: | :----- | #{rows.join("\n")} <!-- Written by `agentilda linear import --commit`. Do not hand-edit the Synced column: it fingerprints what was last pushed, and a row whose fingerprint no longer matches the plan is what triggers an update. --> MARKDOWN end |
Instance Method Details
#all ⇒ Array<Agentilda::Linear::Issue>
Returns possibly empty.
108 |
# File 'lib/agentilda/linear/issue.rb', line 108 def all = @all ||= parse |
#by_unit ⇒ Hash{String => Agentilda::Linear::Issue}
Returns keyed by unit.
111 |
# File 'lib/agentilda/linear/issue.rb', line 111 def by_unit = @by_unit ||= all.to_h { |issue| [issue.unit, issue] } |
#exist? ⇒ Boolean
127 |
# File 'lib/agentilda/linear/issue.rb', line 127 def exist? = File.file?(path) |
#parent ⇒ Agentilda::Linear::Issue?
The issue standing for the folder itself.
121 |
# File 'lib/agentilda/linear/issue.rb', line 121 def parent = by_unit[PARENT] |
#path ⇒ String
Returns absolute path to linear.md.
124 |
# File 'lib/agentilda/linear/issue.rb', line 124 def path = File.join(dir, FILENAME) |
#project ⇒ Hash?
The project this plan was last filed under, if any.
116 |
# File 'lib/agentilda/linear/issue.rb', line 116 def project = (@project ||= [parse_project])[0] |
#write(team:, project:, issues:) ⇒ String
Returns the path written.
133 134 135 136 137 |
# File 'lib/agentilda/linear/issue.rb', line 133 def write(team:, project:, issues:) File.write(path, self.class.render(team:, project:, issues:)) @all = @by_unit = @project = nil path end |