agentilda 1.0.3 → 2.0.1
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.
- checksums.yaml +4 -4
- data/agentilda.gemspec +10 -6
- data/agents/hansolo-reviewer.md +42 -13
- data/agents/lando-broker.md +40 -46
- data/agents/leah-researcher.md +36 -58
- data/agents/luke-backend.md +49 -61
- data/agents/palpatine-planner.md +39 -21
- data/agents/rey-frontend.md +44 -86
- data/agents/yoda-writer.md +38 -36
- data/bin/setup +26 -0
- data/exe/agentilda +13 -92
- data/exe/tilda-rs +0 -0
- data/lib/agentilda/adoption.rb +7 -2
- data/lib/agentilda/agent.rb +40 -99
- data/lib/agentilda/agents.rb +122 -0
- data/lib/agentilda/board.rb +91 -0
- data/lib/agentilda/brief.rb +54 -5
- data/lib/agentilda/child.rb +92 -0
- data/lib/agentilda/cli/agents/subcommands/describe.rb +8 -4
- data/lib/agentilda/cli/base.rb +46 -6
- data/lib/agentilda/cli/create/create.rb +46 -19
- data/lib/agentilda/cli/docs/docs.rb +6 -2
- data/lib/agentilda/cli/index/index.rb +6 -4
- data/lib/agentilda/cli/linear/linear.rb +3 -2
- data/lib/agentilda/cli/linear/subcommands/import.rb +26 -15
- data/lib/agentilda/cli/mail/mail.rb +31 -0
- data/lib/agentilda/cli/mail/subcommands/read.rb +43 -0
- data/lib/agentilda/cli/mail/subcommands/send.rb +43 -0
- data/lib/agentilda/cli/resync/subcommands/dirs.rb +6 -4
- data/lib/agentilda/cli/resync/subcommands/prs.rb +21 -13
- data/lib/agentilda/cli/run/run.rb +207 -99
- data/lib/agentilda/cli/unblock/unblock.rb +24 -13
- data/lib/agentilda/cli/worktree/worktree.rb +85 -0
- data/lib/agentilda/cli.rb +43 -0
- data/lib/agentilda/clock.rb +114 -0
- data/lib/agentilda/console.rb +157 -0
- data/lib/agentilda/control.rb +63 -0
- data/lib/agentilda/creator.rb +7 -2
- data/lib/agentilda/dashboard.rb +219 -0
- data/lib/agentilda/dev_work.rb +2 -0
- data/lib/agentilda/diagram.rb +12 -7
- data/lib/agentilda/dispatcher.rb +646 -0
- data/lib/agentilda/documentation.rb +14 -6
- data/lib/agentilda/executor.rb +362 -204
- data/lib/agentilda/feature.rb +14 -135
- data/lib/agentilda/frontmatter.rb +4 -2
- data/lib/agentilda/github.rb +19 -11
- data/lib/agentilda/index.rb +11 -11
- data/lib/agentilda/keyboard.rb +76 -8
- data/lib/agentilda/launcher.rb +69 -0
- data/lib/agentilda/ledger.rb +233 -0
- data/lib/agentilda/linear/api.rb +14 -10
- data/lib/agentilda/linear/attribution.rb +5 -5
- data/lib/agentilda/linear/import.rb +30 -14
- data/lib/agentilda/linear/issue.rb +6 -3
- data/lib/agentilda/linear/mapping.rb +18 -17
- data/lib/agentilda/linear/push.rb +19 -12
- data/lib/agentilda/linear/survey.rb +3 -2
- data/lib/agentilda/linear/unit.rb +12 -12
- data/lib/agentilda/linear.rb +5 -6
- data/lib/agentilda/mailbox.rb +141 -0
- data/lib/agentilda/markdown.rb +1 -1
- data/lib/agentilda/progress_log.rb +5 -1
- data/lib/agentilda/publisher.rb +6 -3
- data/lib/agentilda/pull_request.rb +0 -186
- data/lib/agentilda/pull_requests.rb +230 -0
- data/lib/agentilda/reporter.rb +9 -3
- data/lib/agentilda/resync.rb +120 -52
- data/lib/agentilda/roster.rb +31 -48
- data/lib/agentilda/runner.rb +72 -389
- data/lib/agentilda/screen/ratatui/bar.rb +57 -0
- data/lib/agentilda/screen/ratatui/key_translator.rb +29 -0
- data/lib/agentilda/screen/ratatui.rb +318 -0
- data/lib/agentilda/state_file.rb +174 -0
- data/lib/agentilda/state_machine.rb +60 -32
- data/lib/agentilda/status.rb +120 -37
- data/lib/agentilda/subject.rb +133 -0
- data/lib/agentilda/tally.rb +17 -11
- data/lib/agentilda/transcript.rb +47 -16
- data/lib/agentilda/tree.rb +10 -6
- data/lib/agentilda/ui.rb +204 -190
- data/lib/agentilda/unblocker.rb +25 -11
- data/lib/agentilda/version.rb +1 -1
- data/lib/agentilda/worktree.rb +14 -18
- data/lib/agentilda.rb +57 -70
- metadata +65 -13
- data/Gemfile +0 -26
- data/Gemfile.lock +0 -261
- data/bin/create-plan-folder +0 -125
- data/lib/dry/cli/banner.rb +0 -293
|
@@ -6,10 +6,12 @@ module Agentilda
|
|
|
6
6
|
class Index < Base
|
|
7
7
|
desc "Create or update .plans/INDEX.md — a global index of every plan."
|
|
8
8
|
|
|
9
|
-
option :output,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
option :output,
|
|
10
|
+
aliases: ["-o"],
|
|
11
|
+
desc: "Write somewhere other than <plans>/INDEX.md; - for STDOUT"
|
|
12
|
+
option :project,
|
|
13
|
+
aliases: ["-p"],
|
|
14
|
+
desc: "Heading for the page, default: the repository's directory name"
|
|
13
15
|
|
|
14
16
|
example [
|
|
15
17
|
" # write .plans/INDEX.md",
|
|
@@ -11,8 +11,9 @@ module Agentilda
|
|
|
11
11
|
class Team < Base
|
|
12
12
|
def self.inherited(klass)
|
|
13
13
|
super
|
|
14
|
-
klass.argument :team,
|
|
15
|
-
|
|
14
|
+
klass.argument :team,
|
|
15
|
+
required: true,
|
|
16
|
+
desc: "The Linear team key that prefixes its issues, e.g. TAX"
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
private
|
|
@@ -8,18 +8,26 @@ module Agentilda
|
|
|
8
8
|
class Import < Team
|
|
9
9
|
desc "Create Linear issues from the plans: one per folder, one child per work unit"
|
|
10
10
|
|
|
11
|
-
option :project,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
option :project,
|
|
12
|
+
aliases: ["-p", "--project-url", "--project-id"],
|
|
13
|
+
desc: "The project to file everything under: its URL, its name, or its id"
|
|
14
|
+
option :commit,
|
|
15
|
+
type: :boolean,
|
|
16
|
+
default: false,
|
|
17
|
+
desc: "Actually create and update in Linear (default: dry run)"
|
|
18
|
+
option :format,
|
|
19
|
+
default: "text",
|
|
20
|
+
values: %w[text json],
|
|
21
|
+
desc: "json emits the exact arguments the Linear MCP tools take"
|
|
22
|
+
option :since,
|
|
23
|
+
aliases: ["-s"],
|
|
24
|
+
desc: "Skip plans numbered below this, e.g. 010.00"
|
|
19
25
|
option :status,
|
|
20
26
|
desc: "Only plans in these states: a comma-separated list of status keys"
|
|
21
|
-
option :force,
|
|
22
|
-
|
|
27
|
+
option :force,
|
|
28
|
+
type: :boolean,
|
|
29
|
+
default: false,
|
|
30
|
+
desc: "Update everything, whether the plan has changed or not"
|
|
23
31
|
|
|
24
32
|
example [
|
|
25
33
|
"TAX -p 'US Tax Law: Self Contained Ruby Gem' # show what would be created",
|
|
@@ -61,7 +69,7 @@ module Agentilda
|
|
|
61
69
|
def resolve_project(team, tree, options)
|
|
62
70
|
reference = options[:project].to_s
|
|
63
71
|
looks_up = reference.match?(%r{\Ahttps?://}) || reference.match?(/\A[0-9a-f-]{32,}\z/)
|
|
64
|
-
return {"id" => nil, "name" => reference, "url" => nil} if !looks_up && offline?
|
|
72
|
+
return { "id" => nil, "name" => reference, "url" => nil } if !looks_up && offline?
|
|
65
73
|
|
|
66
74
|
_api, survey = survey_for(team, tree)
|
|
67
75
|
survey.project(reference)
|
|
@@ -84,8 +92,11 @@ module Agentilda
|
|
|
84
92
|
|
|
85
93
|
# @return [Agentilda::Linear::Import]
|
|
86
94
|
def build(tree, team, project, options)
|
|
87
|
-
Agentilda::Linear::Import.new(tree:,
|
|
88
|
-
|
|
95
|
+
Agentilda::Linear::Import.new(tree:,
|
|
96
|
+
team: Agentilda::Linear.key!(team),
|
|
97
|
+
project:,
|
|
98
|
+
since: options[:since],
|
|
99
|
+
statuses: statuses(options),
|
|
89
100
|
force: options.fetch(:force, false))
|
|
90
101
|
end
|
|
91
102
|
|
|
@@ -116,13 +127,13 @@ module Agentilda
|
|
|
116
127
|
# @return [String]
|
|
117
128
|
def row(action)
|
|
118
129
|
[action.op, action.kind, action.ordinal, action.unit || "-",
|
|
119
|
-
|
|
130
|
+
action.identifier || "-", action.title].join("\t")
|
|
120
131
|
end
|
|
121
132
|
|
|
122
133
|
# @param action [Agentilda::Linear::Action]
|
|
123
134
|
# @return [String]
|
|
124
135
|
def line(action)
|
|
125
|
-
verb = paint(action.op.to_s.ljust(6),
|
|
136
|
+
verb = paint(action.op.to_s.ljust(6), action.op == :create ? :green : :yellow)
|
|
126
137
|
indent = action.child? ? " " : ""
|
|
127
138
|
"#{verb} #{indent}#{action.title} #{paint("(#{action.reason})", :bright_black)}"
|
|
128
139
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentilda
|
|
4
|
+
module CLI
|
|
5
|
+
# `agentilda mail` — the two halves of a pair talking through the plan
|
|
6
|
+
# folder. See {Agentilda::Mailbox} for why a file rather than a session.
|
|
7
|
+
module Mail
|
|
8
|
+
# Both subcommands name a plan and need its folder.
|
|
9
|
+
module Locating
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# An unknown number is refused rather than given an empty mailbox:
|
|
13
|
+
# an agent polling the wrong plan for a whole round would read
|
|
14
|
+
# silence as "my partner has nothing to say".
|
|
15
|
+
#
|
|
16
|
+
# @param options [Hash]
|
|
17
|
+
# @return [Agentilda::Mailbox]
|
|
18
|
+
def mailbox_for(options)
|
|
19
|
+
tree = tree_for(options)
|
|
20
|
+
ordinal = Ordinal.parse(options[:plan].to_s)
|
|
21
|
+
subject = ordinal && tree.find(ordinal)
|
|
22
|
+
unless subject
|
|
23
|
+
refuse("No plan #{options[:plan]} in #{tree.dir}.\n\nKnown: #{tree.ordinals.join(", ")}", 66)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Mailbox.new(dir: subject.feature.path)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentilda
|
|
4
|
+
module CLI
|
|
5
|
+
module Mail
|
|
6
|
+
# `agentilda mail read` — what is waiting for one agent.
|
|
7
|
+
#
|
|
8
|
+
# Nothing waiting is the ordinary result of a poll, so it is not an
|
|
9
|
+
# error and not even STDOUT: the messages are the deliverable, and an
|
|
10
|
+
# empty one is empty.
|
|
11
|
+
class Read < Base
|
|
12
|
+
include Locating
|
|
13
|
+
|
|
14
|
+
desc "Print the messages waiting for an agent in a plan's mailbox"
|
|
15
|
+
|
|
16
|
+
option :plan, required: true, desc: "Which plan: NNN or NNN.MM"
|
|
17
|
+
option :for, required: true, desc: "The agent reading"
|
|
18
|
+
option :after, default: "0", desc: "Only messages numbered above this, so a poll shows what is new"
|
|
19
|
+
|
|
20
|
+
example [
|
|
21
|
+
"--plan 003 --for rey-frontend # everything addressed to rey",
|
|
22
|
+
"--plan 003 --for rey-frontend --after 4 # only what arrived since #4"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# @param options [Hash]
|
|
26
|
+
# @return [void]
|
|
27
|
+
def call(**options)
|
|
28
|
+
quiet?(options)
|
|
29
|
+
mailbox = mailbox_for(options)
|
|
30
|
+
after = options[:after].to_i
|
|
31
|
+
waiting = mailbox.for(options[:for], after:)
|
|
32
|
+
|
|
33
|
+
if waiting.empty?
|
|
34
|
+
UI.line("Nothing for #{options[:for]}#{" after ##{after}" if after.positive?}") unless quiet?(options)
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts waiting.join("\n")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentilda
|
|
4
|
+
module CLI
|
|
5
|
+
module Mail
|
|
6
|
+
# `agentilda mail send` — leave a message for the other half.
|
|
7
|
+
class Send < Base
|
|
8
|
+
include Locating
|
|
9
|
+
|
|
10
|
+
desc "Append a message to a plan's mailbox"
|
|
11
|
+
|
|
12
|
+
argument :body, type: :string, required: true, desc: "The message, quoted"
|
|
13
|
+
|
|
14
|
+
option :plan, required: true, desc: "Which plan: NNN or NNN.MM"
|
|
15
|
+
option :from, required: true, desc: "The agent writing"
|
|
16
|
+
option :to, required: true, desc: "The agent it is for"
|
|
17
|
+
|
|
18
|
+
example [
|
|
19
|
+
%(--plan 003 --from luke-backend --to rey-frontend "GET /returns/:id is up; errors come back as {error: {code, message}}")
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# @param body [String]
|
|
23
|
+
# @param options [Hash]
|
|
24
|
+
# @return [void]
|
|
25
|
+
def call(body:, **options)
|
|
26
|
+
quiet?(options)
|
|
27
|
+
mailbox = mailbox_for(options)
|
|
28
|
+
message = begin
|
|
29
|
+
mailbox.append(from: options[:from], to: options[:to], body:)
|
|
30
|
+
rescue Agentilda::Error => e
|
|
31
|
+
refuse(e.message, 64)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return if quiet?(options)
|
|
35
|
+
|
|
36
|
+
# One line, not a box: an agent runs this between steps, and a
|
|
37
|
+
# four-line frame around "#7 → rey-frontend" is noise it reads.
|
|
38
|
+
UI.line("##{message.number} → #{message.to}, in #{File.basename(mailbox.dir)}/#{Mailbox::FILENAME}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -8,8 +8,10 @@ module Agentilda
|
|
|
8
8
|
class Dirs < Base
|
|
9
9
|
desc "Rename plan folders so the name matches the contents and the NNN.MM form"
|
|
10
10
|
|
|
11
|
-
option :commit,
|
|
12
|
-
|
|
11
|
+
option :commit,
|
|
12
|
+
type: :boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
desc: "Actually rename the folders (default: dry run)"
|
|
13
15
|
|
|
14
16
|
example [
|
|
15
17
|
" # show what would be renamed",
|
|
@@ -20,10 +22,10 @@ module Agentilda
|
|
|
20
22
|
# @return [void]
|
|
21
23
|
def call(**options)
|
|
22
24
|
changes = Agentilda::Resync::Dirs.new(tree: tree_for(options))
|
|
23
|
-
|
|
25
|
+
.call(commit: commit?(options))
|
|
24
26
|
|
|
25
27
|
if changes.empty?
|
|
26
|
-
success("Every folder is already named NNN.MM-<emoji
|
|
28
|
+
success("Every folder is already named NNN.MM-<emoji> → <slug> and the emoji matches.") unless quiet?(options)
|
|
27
29
|
return
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -8,12 +8,18 @@ module Agentilda
|
|
|
8
8
|
class Prs < Base
|
|
9
9
|
desc "Add missing [NNN.MM] prefixes to pull request titles"
|
|
10
10
|
|
|
11
|
-
option :commit,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
desc:
|
|
15
|
-
option :
|
|
16
|
-
|
|
11
|
+
option :commit,
|
|
12
|
+
type: :boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
desc: "Actually retitle the pull requests (default: dry run)"
|
|
15
|
+
option :state,
|
|
16
|
+
default: "all",
|
|
17
|
+
values: %w[open closed merged all],
|
|
18
|
+
desc: "Which pull requests to consider"
|
|
19
|
+
option :adopt,
|
|
20
|
+
type: :boolean,
|
|
21
|
+
default: true,
|
|
22
|
+
desc: "Mint a retroactive plan folder for every pull request that resolves to none. --no-adopt flags them for a human instead"
|
|
17
23
|
|
|
18
24
|
example [
|
|
19
25
|
" # show what would be retitled, and what would be adopted",
|
|
@@ -27,9 +33,11 @@ module Agentilda
|
|
|
27
33
|
def call(**options)
|
|
28
34
|
github = GitHub.new
|
|
29
35
|
tree = tree_for(options)
|
|
30
|
-
changes = Agentilda::Resync::Prs.new(tree:,
|
|
31
|
-
|
|
32
|
-
.
|
|
36
|
+
changes = Agentilda::Resync::Prs.new(tree:,
|
|
37
|
+
github:,
|
|
38
|
+
adopt: options.fetch(:adopt, true),
|
|
39
|
+
root: File.dirname(tree.dir))
|
|
40
|
+
.call(commit: commit?(options))
|
|
33
41
|
|
|
34
42
|
if changes.empty?
|
|
35
43
|
success("Every pull request title already carries a prefix.") unless quiet?(options)
|
|
@@ -58,10 +66,10 @@ module Agentilda
|
|
|
58
66
|
|
|
59
67
|
applicable.each do |c|
|
|
60
68
|
note = if c.adopted?
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
paint(" (new plan)", :magenta)
|
|
70
|
+
elsif c.assumed?
|
|
71
|
+
paint(" (assumed)", :yellow)
|
|
72
|
+
end
|
|
65
73
|
say("##{c.number} #{c.new_title}#{note}")
|
|
66
74
|
end
|
|
67
75
|
|