Class: Agentilda::Linear::Units

Inherits:
Object
  • Object
show all
Defined in:
lib/agentilda/linear/unit.rb

Overview

Reads the work units out of a plan folder.

plan.md is written by an agent for humans, not as a data file, so this reads the one structure the format has always had: a heading per unit, naming the pull request it will become. Everything else is prose about those units.

Two things about real plans make that harder than it sounds, and both were found by running this over thirty-eight of them:

* The unit headings are all at one level, but *which* level varies by
document — `## PR-1 — …` in one plan, `### PR 020.01 — …` in the
next. Deeper headings underneath them (`### PR-1 tests`) name the
same unit again. Matching every heading that mentions a pull request
turned one plan's three units into nine.
* A plan numbers its units either from one (`PR-1`) or from its own
spec number (`PR 020.01`). The second form is the first form with
the plan's number glued on, so it is normalised back.

A plan with no such headings is not an error. Retroactive plans have no plan.md at all, and a small plan is often one pull request with no internal divisions — both import as a single issue standing for the whole plan, which is the honest reading of a plan that never divided itself.

Constant Summary collapse

HEADING =

## 2. PR-1 — …, ### PR 020.01 — …, and the several other ways the same heading gets typed. The hashes are escaped because an unescaped #{ in a regexp literal is interpolation.

/\A(\#{2,4})[ \t]+(?:\d+[.)][ \t]*)?PR[\s_-]?(\d+(?:\.\d+)?)\b[ \t]*(.*)\z/i
FENCE =

A fenced block can hold anything, including lines that read as headings — plan.md files are full of shell and SQL whose comments start with #. Scanning without stripping fences invents units.

/^[ \t]{0,3}(?:```|~~~)/
GLYPH_NOTE =

(✔ #38), (▶︎ in-flight), (⬜ blocked) — a status glyph in a trailing parenthetical. It is the state of the unit, which Linear tracks itself, and leaving it in the title means every title churns the moment the work moves.

/\s*\((?:[^\w\s(][^)]*)\)\s*\z/
TRAILING_GLYPH =

The same thing without the brackets: … over the law plane ☢️ deferred.

/\s*[\u{2190}-\u{2BFF}\u{1F000}-\u{1FAFF}][^\n]*\z/
OWN_NUMBER =

A plan number the author typed into the heading as well. The issue for the plan already carries it.

/\A\[\d{1,3}(?:\.\d{2})?\]\s*/
NUMBERED =

Pull request numbers named in the heading: "(✔ #38)".

/#(\d+)\b/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject:) ⇒ Units

Returns a new instance of Units.

Parameters:



69
70
71
# File 'lib/agentilda/linear/unit.rb', line 69

def initialize(subject:)
  @subject = subject
end

Class Method Details

.clean_title(title) ⇒ String

A pull request title carries the plan number this tool put there, and often the unit it implements. Neither belongs in an issue title: the issue for the plan already says which plan, and the attachment already says which pull request.

Parameters:

  • title (String)

Returns:

  • (String)


94
95
96
97
98
99
100
# File 'lib/agentilda/linear/unit.rb', line 94

def self.clean_title(title)
  title.to_s
    .sub(/\A\[[^\]]*\]\s*/, "")
    .sub(/\A(?:spec\s*)?\d{1,3}(?:\.\d{2})?\s*[—–:.-]?\s*/i, "")
    .sub(/\APR[\s_-]?\d+(?:\.\d+)?\s*[—–:.-]?\s*/i, "")
    .strip
end

Instance Method Details

#allArray<Agentilda::Linear::Unit>

The units of work inside a plan, which become its sub-issues.

A plan that declares its units in plan.md gets those. A plan that does not — a retroactive one, or one written before the convention — gets one per pull request instead, because a pull request that shipped is a unit of work whether or not anybody wrote it down first.

A plan with neither is legitimately empty: it has been specified and nothing has been divided or built yet. Its issue stands alone until somebody plans it.

Returns:



85
# File 'lib/agentilda/linear/unit.rb', line 85

def all = @all ||= divided.empty? ? from_pull_requests : attach(divided)