Class: Agentilda::Linear::Attribution

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

Overview

Which plan a pull request belongs to, when its title does not say.

resync prs puts an [NNN.MM] on every title it can resolve, and most of them resolve. What is left is the tail: work that shipped before the convention existed, or from a branch named after nothing in particular. Those pull requests are real work with no home, and dropping them means the issues that stand for a plan are missing pieces of it.

The rule is word overlap between the pull request's title and the folder's name. It works here, where it did not work for matching plans to projects, because the candidates are different: a folder slug is ledger-carryforward-vintages, three specific words about one thing, and there are twenty of them to choose between. A project is called "US Tax Law: Self Contained Ruby Gem" and there are four.

It is still a guess, and it says so. A tie is refused rather than broken, because two folders matching equally well is evidence that neither is right rather than a reason to pick the first.

Defined Under Namespace

Classes: Placed

Constant Summary collapse

FLOOR =

Words too common in this domain to carry a match on their own. Every plan in a tax engine says "tax"; a pull request that says it too has told you nothing. How much of a folder's name a title must cover to claim it.

0.5
WIDER_FLOOR =

And how much a description must cover, which is more.

A title is written to say what the change is. A description is written to get the change reviewed, and it opens with whatever this repository puts at the top of every one of them — a stacking note, a diff summary, a checklist. Read at the same bar as a title it produced two placements here and both were wrong, one of them on the word "documents" inside "diff includes nineteen documents".

0.75
MINIMUM_WORDS =

Words, not just a fraction. Half of a two-word folder name is one word, which is the case that was already established as too thin — "core" alone claiming deterministic-core-and-as-of.

3
NOISE =
%w[the and for with from into that this add adds added fix fixes
update updates use uses spec plan pull request pr tax app web api].freeze
OPENING_WORDS =

How much of a pull request's description to read when its title was not enough. The opening sentence or two of a description says what the change is for; further down it turns into checklists, test plans and generated tables, which are the same words on every pull request in the repository and match everything equally.

20

Instance Method Summary collapse

Constructor Details

#initialize(tree:) ⇒ Attribution

Returns a new instance of Attribution.

Parameters:



81
82
83
# File 'lib/agentilda/linear/attribution.rb', line 81

def initialize(tree:)
  @tree = tree
end

Instance Method Details

#call(pulls, bodies: {}) ⇒ Array<Agentilda::Linear::Attribution::Placed>

Parameters:

  • pulls (Array<Agentilda::PullRequest>)

    the ones with no number

  • bodies (Hash{String => String}) (defaults to: {})

    descriptions, by pull request number

Returns:



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/agentilda/linear/attribution.rb', line 95

def call(pulls, bodies: {})
  pulls.map do |pull|
    found = place(pull, words(pull.title))
    next found if found.placed?

    # Second pass. A title is a headline and sometimes says nothing
    # useful — "Add the Drake reference-return worklist" names a vendor
    # rather than the work. The description usually opens by saying what
    # the change is actually about.
    opening = opening_words(bodies[pull.number.to_s])
    next found if opening.empty?

    wider = place(pull, words(pull.title) | opening, floor: WIDER_FLOOR)
    wider.placed? ? wider.with(widened: true) : found
  end
end

#opening_words(body) ⇒ Array<String>

Parameters:

  • body (String, nil)

Returns:

  • (Array<String>)


114
115
116
117
118
119
120
# File 'lib/agentilda/linear/attribution.rb', line 114

def opening_words(body)
  text = body.to_s
    .gsub(/<!--.*?-->/m, "")
    .gsub(/```.*?```/m, "")
    .gsub(/^\s*[-*|#>]+/, " ")
  words(text.split(/\s+/).first(OPENING_WORDS * 3).join(" ")).first(OPENING_WORDS)
end