agentilda 2.0.3 → 2.0.4
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/lib/agentilda/agents/agent.rb +79 -0
- data/lib/agentilda/agents/registry.rb +121 -0
- data/lib/agentilda/agents/roster.rb +92 -0
- data/lib/agentilda/agents.rb +11 -113
- data/lib/agentilda/cli/agents/subcommands/describe.rb +3 -3
- data/lib/agentilda/cli/agents/subcommands/list.rb +1 -1
- data/lib/agentilda/cli/base.rb +3 -3
- data/lib/agentilda/cli/create/create.rb +17 -18
- data/lib/agentilda/cli/docs/docs.rb +1 -1
- data/lib/agentilda/cli/index/index.rb +1 -1
- data/lib/agentilda/cli/linear/subcommands/import.rb +1 -1
- data/lib/agentilda/cli/list_plans/list_plans.rb +1 -1
- data/lib/agentilda/cli/mail/mail.rb +4 -4
- data/lib/agentilda/cli/mail/subcommands/send.rb +1 -1
- data/lib/agentilda/cli/resync/subcommands/dirs.rb +2 -2
- data/lib/agentilda/cli/resync/subcommands/prs.rb +3 -3
- data/lib/agentilda/cli/run/run.rb +40 -40
- data/lib/agentilda/cli/states/states.rb +1 -1
- data/lib/agentilda/cli/unblock/unblock.rb +17 -17
- data/lib/agentilda/cli/worktree/worktree.rb +4 -4
- data/lib/agentilda/cli.rb +1 -1
- data/lib/agentilda/engine/board.rb +93 -0
- data/lib/agentilda/engine/dispatcher.rb +646 -0
- data/lib/agentilda/engine/progress_log.rb +125 -0
- data/lib/agentilda/engine/runner.rb +141 -0
- data/lib/agentilda/engine/state_file.rb +176 -0
- data/lib/agentilda/engine/tally.rb +177 -0
- data/lib/agentilda/engine.rb +19 -0
- data/lib/agentilda/execution/child.rb +94 -0
- data/lib/agentilda/execution/clock.rb +116 -0
- data/lib/agentilda/execution/control.rb +180 -0
- data/lib/agentilda/execution/executor.rb +699 -0
- data/lib/agentilda/execution/transcript.rb +468 -0
- data/lib/agentilda/execution.rb +18 -0
- data/lib/agentilda/lifecycle/adoption.rb +197 -0
- data/lib/agentilda/lifecycle/brief.rb +285 -0
- data/lib/agentilda/lifecycle/creator.rb +125 -0
- data/lib/agentilda/lifecycle/resync.rb +428 -0
- data/lib/agentilda/lifecycle/unblocker.rb +223 -0
- data/lib/agentilda/lifecycle.rb +28 -0
- data/lib/agentilda/linear/api.rb +1 -1
- data/lib/agentilda/linear/attribution.rb +7 -7
- data/lib/agentilda/linear/import.rb +18 -18
- data/lib/agentilda/linear/mapping.rb +6 -6
- data/lib/agentilda/linear/push.rb +5 -5
- data/lib/agentilda/linear/survey.rb +7 -7
- data/lib/agentilda/linear/unit.rb +5 -5
- data/lib/agentilda/plans/dev_work.rb +56 -0
- data/lib/agentilda/plans/feature.rb +131 -0
- data/lib/agentilda/plans/index.rb +208 -0
- data/lib/agentilda/plans/ledger.rb +235 -0
- data/lib/agentilda/plans/mailbox.rb +143 -0
- data/lib/agentilda/plans/ordinal.rb +92 -0
- data/lib/agentilda/plans/pull_request.rb +29 -0
- data/lib/agentilda/plans/pull_requests.rb +229 -0
- data/lib/agentilda/plans/state_machine.rb +383 -0
- data/lib/agentilda/plans/status.rb +365 -0
- data/lib/agentilda/plans/subject.rb +131 -0
- data/lib/agentilda/plans/tree.rb +79 -0
- data/lib/agentilda/plans.rb +39 -0
- data/lib/agentilda/presentation/console.rb +159 -0
- data/lib/agentilda/presentation/dashboard.rb +221 -0
- data/lib/agentilda/presentation/diagram.rb +149 -0
- data/lib/agentilda/presentation/documentation.rb +439 -0
- data/lib/agentilda/presentation/keyboard.rb +158 -0
- data/lib/agentilda/presentation/reporter.rb +181 -0
- data/lib/agentilda/presentation/screen/ratatui/bar.rb +59 -0
- data/lib/agentilda/presentation/screen/ratatui/key_translator.rb +31 -0
- data/lib/agentilda/presentation/screen/ratatui.rb +320 -0
- data/lib/agentilda/presentation/viewer.rb +62 -0
- data/lib/agentilda/presentation.rb +23 -0
- data/lib/agentilda/{ui.rb → support/ui.rb} +5 -5
- data/lib/agentilda/vcs/github.rb +170 -0
- data/lib/agentilda/vcs/publisher.rb +177 -0
- data/lib/agentilda/vcs/worktree.rb +209 -0
- data/lib/agentilda/vcs.rb +19 -0
- data/lib/agentilda/version.rb +1 -1
- data/lib/agentilda.rb +6 -5
- metadata +56 -49
- data/lib/agentilda/adoption.rb +0 -197
- data/lib/agentilda/agent.rb +0 -77
- data/lib/agentilda/board.rb +0 -91
- data/lib/agentilda/brief.rb +0 -283
- data/lib/agentilda/child.rb +0 -92
- data/lib/agentilda/clock.rb +0 -114
- data/lib/agentilda/console.rb +0 -157
- data/lib/agentilda/control.rb +0 -178
- data/lib/agentilda/creator.rb +0 -125
- data/lib/agentilda/dashboard.rb +0 -219
- data/lib/agentilda/dev_work.rb +0 -56
- data/lib/agentilda/diagram.rb +0 -149
- data/lib/agentilda/dispatcher.rb +0 -646
- data/lib/agentilda/documentation.rb +0 -437
- data/lib/agentilda/executor.rb +0 -697
- data/lib/agentilda/feature.rb +0 -132
- data/lib/agentilda/github.rb +0 -168
- data/lib/agentilda/index.rb +0 -206
- data/lib/agentilda/keyboard.rb +0 -156
- data/lib/agentilda/ledger.rb +0 -233
- data/lib/agentilda/mailbox.rb +0 -141
- data/lib/agentilda/ordinal.rb +0 -90
- data/lib/agentilda/progress_log.rb +0 -126
- data/lib/agentilda/publisher.rb +0 -175
- data/lib/agentilda/pull_request.rb +0 -27
- data/lib/agentilda/pull_requests.rb +0 -230
- data/lib/agentilda/reporter.rb +0 -181
- data/lib/agentilda/resync.rb +0 -426
- data/lib/agentilda/roster.rb +0 -93
- data/lib/agentilda/runner.rb +0 -139
- data/lib/agentilda/screen/ratatui/bar.rb +0 -57
- data/lib/agentilda/screen/ratatui/key_translator.rb +0 -29
- data/lib/agentilda/screen/ratatui.rb +0 -318
- data/lib/agentilda/state_file.rb +0 -174
- data/lib/agentilda/state_machine.rb +0 -383
- data/lib/agentilda/status.rb +0 -363
- data/lib/agentilda/subject.rb +0 -133
- data/lib/agentilda/tally.rb +0 -175
- data/lib/agentilda/transcript.rb +0 -466
- data/lib/agentilda/tree.rb +0 -81
- data/lib/agentilda/unblocker.rb +0 -221
- data/lib/agentilda/viewer.rb +0 -60
- data/lib/agentilda/worktree.rb +0 -207
- /data/lib/agentilda/{config.rb → support/config.rb} +0 -0
- /data/lib/agentilda/{frontmatter.rb → support/frontmatter.rb} +0 -0
- /data/lib/agentilda/{markdown.rb → support/markdown.rb} +0 -0
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agentilda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Gredeskoul
|
|
@@ -294,12 +294,10 @@ files:
|
|
|
294
294
|
- exe/tilda
|
|
295
295
|
- exe/tilda-rs
|
|
296
296
|
- lib/agentilda.rb
|
|
297
|
-
- lib/agentilda/adoption.rb
|
|
298
|
-
- lib/agentilda/agent.rb
|
|
299
297
|
- lib/agentilda/agents.rb
|
|
300
|
-
- lib/agentilda/
|
|
301
|
-
- lib/agentilda/
|
|
302
|
-
- lib/agentilda/
|
|
298
|
+
- lib/agentilda/agents/agent.rb
|
|
299
|
+
- lib/agentilda/agents/registry.rb
|
|
300
|
+
- lib/agentilda/agents/roster.rb
|
|
303
301
|
- lib/agentilda/cli.rb
|
|
304
302
|
- lib/agentilda/cli/agents/subcommands/describe.rb
|
|
305
303
|
- lib/agentilda/cli/agents/subcommands/list.rb
|
|
@@ -321,24 +319,26 @@ files:
|
|
|
321
319
|
- lib/agentilda/cli/unblock/unblock.rb
|
|
322
320
|
- lib/agentilda/cli/version/version.rb
|
|
323
321
|
- lib/agentilda/cli/worktree/worktree.rb
|
|
324
|
-
- lib/agentilda/
|
|
325
|
-
- lib/agentilda/
|
|
326
|
-
- lib/agentilda/
|
|
327
|
-
- lib/agentilda/
|
|
328
|
-
- lib/agentilda/
|
|
329
|
-
- lib/agentilda/
|
|
330
|
-
- lib/agentilda/
|
|
331
|
-
- lib/agentilda/
|
|
332
|
-
- lib/agentilda/
|
|
333
|
-
- lib/agentilda/
|
|
334
|
-
- lib/agentilda/
|
|
335
|
-
- lib/agentilda/
|
|
336
|
-
- lib/agentilda/
|
|
337
|
-
- lib/agentilda/github.rb
|
|
338
|
-
- lib/agentilda/index.rb
|
|
339
|
-
- lib/agentilda/keyboard.rb
|
|
322
|
+
- lib/agentilda/engine.rb
|
|
323
|
+
- lib/agentilda/engine/board.rb
|
|
324
|
+
- lib/agentilda/engine/dispatcher.rb
|
|
325
|
+
- lib/agentilda/engine/progress_log.rb
|
|
326
|
+
- lib/agentilda/engine/runner.rb
|
|
327
|
+
- lib/agentilda/engine/state_file.rb
|
|
328
|
+
- lib/agentilda/engine/tally.rb
|
|
329
|
+
- lib/agentilda/execution.rb
|
|
330
|
+
- lib/agentilda/execution/child.rb
|
|
331
|
+
- lib/agentilda/execution/clock.rb
|
|
332
|
+
- lib/agentilda/execution/control.rb
|
|
333
|
+
- lib/agentilda/execution/executor.rb
|
|
334
|
+
- lib/agentilda/execution/transcript.rb
|
|
340
335
|
- lib/agentilda/launcher.rb
|
|
341
|
-
- lib/agentilda/
|
|
336
|
+
- lib/agentilda/lifecycle.rb
|
|
337
|
+
- lib/agentilda/lifecycle/adoption.rb
|
|
338
|
+
- lib/agentilda/lifecycle/brief.rb
|
|
339
|
+
- lib/agentilda/lifecycle/creator.rb
|
|
340
|
+
- lib/agentilda/lifecycle/resync.rb
|
|
341
|
+
- lib/agentilda/lifecycle/unblocker.rb
|
|
342
342
|
- lib/agentilda/linear.rb
|
|
343
343
|
- lib/agentilda/linear/api.rb
|
|
344
344
|
- lib/agentilda/linear/attribution.rb
|
|
@@ -349,32 +349,39 @@ files:
|
|
|
349
349
|
- lib/agentilda/linear/push.rb
|
|
350
350
|
- lib/agentilda/linear/survey.rb
|
|
351
351
|
- lib/agentilda/linear/unit.rb
|
|
352
|
-
- lib/agentilda/
|
|
353
|
-
- lib/agentilda/
|
|
354
|
-
- lib/agentilda/
|
|
355
|
-
- lib/agentilda/
|
|
356
|
-
- lib/agentilda/
|
|
357
|
-
- lib/agentilda/
|
|
358
|
-
- lib/agentilda/
|
|
359
|
-
- lib/agentilda/
|
|
360
|
-
- lib/agentilda/
|
|
361
|
-
- lib/agentilda/
|
|
362
|
-
- lib/agentilda/
|
|
363
|
-
- lib/agentilda/
|
|
364
|
-
- lib/agentilda/
|
|
365
|
-
- lib/agentilda/
|
|
366
|
-
- lib/agentilda/
|
|
367
|
-
- lib/agentilda/
|
|
368
|
-
- lib/agentilda/
|
|
369
|
-
- lib/agentilda/
|
|
370
|
-
- lib/agentilda/
|
|
371
|
-
- lib/agentilda/
|
|
372
|
-
- lib/agentilda/
|
|
373
|
-
- lib/agentilda/
|
|
374
|
-
- lib/agentilda/
|
|
352
|
+
- lib/agentilda/plans.rb
|
|
353
|
+
- lib/agentilda/plans/dev_work.rb
|
|
354
|
+
- lib/agentilda/plans/feature.rb
|
|
355
|
+
- lib/agentilda/plans/index.rb
|
|
356
|
+
- lib/agentilda/plans/ledger.rb
|
|
357
|
+
- lib/agentilda/plans/mailbox.rb
|
|
358
|
+
- lib/agentilda/plans/ordinal.rb
|
|
359
|
+
- lib/agentilda/plans/pull_request.rb
|
|
360
|
+
- lib/agentilda/plans/pull_requests.rb
|
|
361
|
+
- lib/agentilda/plans/state_machine.rb
|
|
362
|
+
- lib/agentilda/plans/status.rb
|
|
363
|
+
- lib/agentilda/plans/subject.rb
|
|
364
|
+
- lib/agentilda/plans/tree.rb
|
|
365
|
+
- lib/agentilda/presentation.rb
|
|
366
|
+
- lib/agentilda/presentation/console.rb
|
|
367
|
+
- lib/agentilda/presentation/dashboard.rb
|
|
368
|
+
- lib/agentilda/presentation/diagram.rb
|
|
369
|
+
- lib/agentilda/presentation/documentation.rb
|
|
370
|
+
- lib/agentilda/presentation/keyboard.rb
|
|
371
|
+
- lib/agentilda/presentation/reporter.rb
|
|
372
|
+
- lib/agentilda/presentation/screen/ratatui.rb
|
|
373
|
+
- lib/agentilda/presentation/screen/ratatui/bar.rb
|
|
374
|
+
- lib/agentilda/presentation/screen/ratatui/key_translator.rb
|
|
375
|
+
- lib/agentilda/presentation/viewer.rb
|
|
376
|
+
- lib/agentilda/support/config.rb
|
|
377
|
+
- lib/agentilda/support/frontmatter.rb
|
|
378
|
+
- lib/agentilda/support/markdown.rb
|
|
379
|
+
- lib/agentilda/support/ui.rb
|
|
380
|
+
- lib/agentilda/vcs.rb
|
|
381
|
+
- lib/agentilda/vcs/github.rb
|
|
382
|
+
- lib/agentilda/vcs/publisher.rb
|
|
383
|
+
- lib/agentilda/vcs/worktree.rb
|
|
375
384
|
- lib/agentilda/version.rb
|
|
376
|
-
- lib/agentilda/viewer.rb
|
|
377
|
-
- lib/agentilda/worktree.rb
|
|
378
385
|
homepage: https://github.com/kigster/agentilda
|
|
379
386
|
licenses: []
|
|
380
387
|
metadata:
|
|
@@ -395,7 +402,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
395
402
|
- !ruby/object:Gem::Version
|
|
396
403
|
version: '0'
|
|
397
404
|
requirements: []
|
|
398
|
-
rubygems_version: 4.0.
|
|
405
|
+
rubygems_version: 4.0.22
|
|
399
406
|
specification_version: 4
|
|
400
407
|
summary: 'Agentic specification-driven development: spec, plan, build, review'
|
|
401
408
|
test_files: []
|
data/lib/agentilda/adoption.rb
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "parallel"
|
|
5
|
-
|
|
6
|
-
require "agentilda/status"
|
|
7
|
-
|
|
8
|
-
module Agentilda
|
|
9
|
-
# Gives a plan folder to every pull request that has no plan to point at.
|
|
10
|
-
#
|
|
11
|
-
# `resync prs` can resolve most pull requests from their branch or their
|
|
12
|
-
# diff. What it cannot resolve it used to flag for a human — "diff touches
|
|
13
|
-
# 021.00, 022.00, 024.00 and none is obviously primary" — and stop. That is
|
|
14
|
-
# honest but not useful: the work exists, it is unspecified, and leaving it
|
|
15
|
-
# unnumbered means it never appears in the index at all.
|
|
16
|
-
#
|
|
17
|
-
# So an orphan is adopted instead. It gets a retroactive plan of its own,
|
|
18
|
-
# numbered in the gap after the furthest plan it can see, holding the pull
|
|
19
|
-
# request that produced it.
|
|
20
|
-
#
|
|
21
|
-
# == Why the numbering is allocated serially
|
|
22
|
-
#
|
|
23
|
-
# The obvious parallel design — every worker reads the highest number and
|
|
24
|
-
# adds `.01` — is deterministic but not unique, and the collision is the
|
|
25
|
-
# common case rather than the rare one. Fifteen open pull requests branched
|
|
26
|
-
# off the same main all see the same highest number and all compute the same
|
|
27
|
-
# slot. Determinism is not the property that matters here; distinctness is.
|
|
28
|
-
#
|
|
29
|
-
# So the pipeline is **gather in parallel, allocate serially, apply in
|
|
30
|
-
# parallel**. The allocation is pure arithmetic over an array that is
|
|
31
|
-
# already in memory, so the serial section costs microseconds; the two
|
|
32
|
-
# expensive phases — reading each branch and writing each folder — keep the
|
|
33
|
-
# whole fan-out. And because the slots are handed out before any worker
|
|
34
|
-
# starts, no worker can want a number another worker holds: no locks, no
|
|
35
|
-
# retries, and the same input produces the same numbers every run.
|
|
36
|
-
class Adoption
|
|
37
|
-
# One pull request and the plan it is being given.
|
|
38
|
-
#
|
|
39
|
-
# @!attribute [r] pull
|
|
40
|
-
# @return [Hash] as returned by {GitHub#pulls}
|
|
41
|
-
# @!attribute [r] ordinal
|
|
42
|
-
# @return [Agentilda::Ordinal] the slot it was allocated
|
|
43
|
-
# @!attribute [r] major
|
|
44
|
-
# @return [Integer] the furthest plan its branch or diff could see
|
|
45
|
-
# @!attribute [r] path
|
|
46
|
-
# @return [String, nil] the folder, once created
|
|
47
|
-
Adoptee = Data.define(:pull, :ordinal, :major, :path) do
|
|
48
|
-
# @return [String] the folder name this pull request earns
|
|
49
|
-
def dirname = Agentilda.plan_dirname(ordinal, STATUS_BY_KEY.fetch(:retroactive), slug)
|
|
50
|
-
|
|
51
|
-
# The PR title, stripped of any prefix, as the folder's tail.
|
|
52
|
-
#
|
|
53
|
-
# @return [String]
|
|
54
|
-
def slug = Creator.slugify(pull[:title].to_s.sub(/\A\[[^\]]+\](?:\([A-Z]\))?\s*/, ""))
|
|
55
|
-
|
|
56
|
-
# @return [String] a single auditable line
|
|
57
|
-
def to_s = "##{pull[:number]} → #{dirname}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# @param tree [Agentilda::Tree]
|
|
61
|
-
# @param github [Agentilda::GitHub]
|
|
62
|
-
# @param root [String] repository root, for reading branches
|
|
63
|
-
# @param jobs [Integer] workers for the two parallel phases
|
|
64
|
-
def initialize(tree:, github: GitHub.new, root: nil, jobs: UI.default_jobs)
|
|
65
|
-
@tree = tree
|
|
66
|
-
@github = github
|
|
67
|
-
@root = root || File.dirname(tree.dir)
|
|
68
|
-
@jobs = jobs
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# @return [Agentilda::Tree]
|
|
72
|
-
attr_reader :tree
|
|
73
|
-
|
|
74
|
-
# @return [Agentilda::GitHub]
|
|
75
|
-
attr_reader :github
|
|
76
|
-
|
|
77
|
-
# @return [String]
|
|
78
|
-
attr_reader :root
|
|
79
|
-
|
|
80
|
-
# @return [Integer]
|
|
81
|
-
attr_reader :jobs
|
|
82
|
-
|
|
83
|
-
# Work out what each orphan would be given, without creating anything.
|
|
84
|
-
#
|
|
85
|
-
# @param pulls [Array<Hash>] the orphans, from {Resync::Prs}
|
|
86
|
-
# @return [Array<Agentilda::Adoption::Adoptee>] in pull request order
|
|
87
|
-
def plan(pulls)
|
|
88
|
-
allocate(gather(pulls))
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Adopt every orphan: create its folder and record its pull request.
|
|
92
|
-
#
|
|
93
|
-
# @param pulls [Array<Hash>]
|
|
94
|
-
# @return [Array<Agentilda::Adoption::Adoptee>] with `path` filled in
|
|
95
|
-
def call(pulls)
|
|
96
|
-
adoptees = plan(pulls)
|
|
97
|
-
return adoptees if adoptees.empty?
|
|
98
|
-
|
|
99
|
-
created = Parallel.map(adoptees, in_threads: jobs) { |adoptee| adopt(adoptee) }
|
|
100
|
-
tree.reload
|
|
101
|
-
created
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
private
|
|
105
|
-
|
|
106
|
-
# Phase one, in parallel: ask each pull request's branch how far the
|
|
107
|
-
# sequence had got when the work started. Branches are not all rebased
|
|
108
|
-
# onto the same main, so this is per-branch rather than tree-wide, and it
|
|
109
|
-
# is a network-free `ls-tree` per pull request.
|
|
110
|
-
#
|
|
111
|
-
# @param pulls [Array<Hash>]
|
|
112
|
-
# @return [Array<Array(Hash, Integer)>] each pull with its major
|
|
113
|
-
def gather(pulls)
|
|
114
|
-
ordered = pulls.sort_by { |pull| pull[:number].to_i }
|
|
115
|
-
|
|
116
|
-
Parallel.map(ordered, in_threads: jobs) { |pull| [pull, major_for(pull)] }
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# The furthest whole plan this pull request can see: on its own branch
|
|
120
|
-
# first, then whatever it touched, then the tree we are standing in. A
|
|
121
|
-
# pull request whose branch is long gone still has a diff.
|
|
122
|
-
#
|
|
123
|
-
# @param pull [Hash]
|
|
124
|
-
# @return [Integer]
|
|
125
|
-
def major_for(pull)
|
|
126
|
-
candidates = Agentilda.plans_on_ref(root, pull[:branch].to_s)
|
|
127
|
-
candidates = touched(pull) if candidates.empty?
|
|
128
|
-
candidates = tree.subjects.map { |s| s.feature.ordinal } if candidates.empty?
|
|
129
|
-
|
|
130
|
-
candidates.map(&:major).max.to_i
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# @param pull [Hash]
|
|
134
|
-
# @return [Array<Agentilda::Ordinal>]
|
|
135
|
-
def touched(pull)
|
|
136
|
-
Array(pull[:files]).filter_map { |path|
|
|
137
|
-
parts = path.to_s.split("/")
|
|
138
|
-
index = parts.index(Agentilda::PLANS_DIR)
|
|
139
|
-
index && parts[index + 1] && Ordinal.from_dirname(parts[index + 1])
|
|
140
|
-
}.uniq
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Phase two, serial and deliberately so. Minors are handed out per major,
|
|
144
|
-
# continuing past every slot the tree already holds, in ascending pull
|
|
145
|
-
# request order — so the numbers follow the order the work was opened in,
|
|
146
|
-
# and two pull requests cannot be given the same one.
|
|
147
|
-
#
|
|
148
|
-
# @param gathered [Array<Array(Hash, Integer)>]
|
|
149
|
-
# @return [Array<Agentilda::Adoption::Adoptee>]
|
|
150
|
-
def allocate(gathered)
|
|
151
|
-
taken = Hash.new { |h, major| h[major] = minors_taken(major) }
|
|
152
|
-
|
|
153
|
-
gathered.filter_map do |pull, major|
|
|
154
|
-
minor = ((taken[major].max || 0) + 1)
|
|
155
|
-
next if minor > Ordinal::MAX_MINOR
|
|
156
|
-
|
|
157
|
-
taken[major] << minor
|
|
158
|
-
Adoptee.new(pull:, major:, ordinal: Ordinal.new(major:, minor:), path: nil)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# @param major [Integer]
|
|
163
|
-
# @return [Array<Integer>] minors already used under this major
|
|
164
|
-
def minors_taken(major)
|
|
165
|
-
tree.subjects.map { |s| s.feature.ordinal }.select { |o| o.major == major }.map(&:minor)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Phase three, in parallel: each worker owns a number nobody else can
|
|
169
|
-
# want, so it can create its folder without coordinating with anyone.
|
|
170
|
-
#
|
|
171
|
-
# @param adoptee [Agentilda::Adoption::Adoptee]
|
|
172
|
-
# @return [Agentilda::Adoption::Adoptee]
|
|
173
|
-
def adopt(adoptee)
|
|
174
|
-
path = File.join(tree.dir, adoptee.dirname)
|
|
175
|
-
return adoptee if File.exist?(path)
|
|
176
|
-
|
|
177
|
-
FileUtils.mkdir_p(path)
|
|
178
|
-
File.write(File.join(path, PullRequests::FILENAME),
|
|
179
|
-
PullRequests.render([pull_request_for(adoptee)]))
|
|
180
|
-
|
|
181
|
-
adoptee.with(path:)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# The body is what a specification would be written from, so it is worth
|
|
185
|
-
# a second request. A failure here costs the description, not the folder.
|
|
186
|
-
#
|
|
187
|
-
# @param adoptee [Agentilda::Adoption::Adoptee]
|
|
188
|
-
# @return [Hash]
|
|
189
|
-
def pull_request_for(adoptee)
|
|
190
|
-
pull = adoptee.pull
|
|
191
|
-
github.pull_request(pull[:number].to_s)
|
|
192
|
-
rescue Agentilda::Error
|
|
193
|
-
{ number: pull[:number], title: pull[:title], url: pull[:url],
|
|
194
|
-
state: pull[:state] || "Unknown", body: "" }
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
data/lib/agentilda/agent.rb
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Agentilda
|
|
4
|
-
# One specialist, loaded from `~/.agents/agents/<name>.md`.
|
|
5
|
-
#
|
|
6
|
-
# The definition files are the single source of truth for who does what: the
|
|
7
|
-
# frontmatter says which plan states this agent handles and what it may write,
|
|
8
|
-
# and the body is the prompt. Nothing about a specialty is duplicated in Ruby.
|
|
9
|
-
#
|
|
10
|
-
# @!attribute [r] name
|
|
11
|
-
# @return [String]
|
|
12
|
-
# @!attribute [r] description
|
|
13
|
-
# @return [String]
|
|
14
|
-
# @!attribute [r] handles
|
|
15
|
-
# @return [Array<Symbol>] plan states this agent is offered work from
|
|
16
|
-
# @!attribute [r] advances_to
|
|
17
|
-
# @return [Symbol, nil] the state it is expected to reach; nil = read-only
|
|
18
|
-
# @!attribute [r] model
|
|
19
|
-
# @return [String, nil]
|
|
20
|
-
# @!attribute [r] allowed_tools
|
|
21
|
-
# @return [Array<String>]
|
|
22
|
-
# @!attribute [r] may
|
|
23
|
-
# @return [Array<String>] commands lifted from {Executor::FORBIDDEN_COMMANDS}
|
|
24
|
-
# for this agent alone. Nothing in {Executor::UNGRANTABLE} can be lifted.
|
|
25
|
-
# @!attribute [r] timeout
|
|
26
|
-
# @return [Integer, nil] seconds before this agent is abandoned; nil
|
|
27
|
-
# defers to the executor's run-wide default
|
|
28
|
-
# @!attribute [r] ledger
|
|
29
|
-
# @return [Array<String>] the documents this agent signs, in the order
|
|
30
|
-
# it reaches them; the last entry it wrote is where it stands
|
|
31
|
-
# @!attribute [r] rounds
|
|
32
|
-
# @return [Integer] how many times it may be invoked on one plan in one
|
|
33
|
-
# state before the plan parks; a Completed never earns another
|
|
34
|
-
# @!attribute [r] effort
|
|
35
|
-
# @return [String, nil] passed to `claude --effort`
|
|
36
|
-
# @!attribute [r] starts_as
|
|
37
|
-
# @return [Symbol, nil] the state the harness renames the plan into the
|
|
38
|
-
# moment this agent is dispatched, where the topology permits it
|
|
39
|
-
# @!attribute [r] holds_at
|
|
40
|
-
# @return [Symbol, nil] the state the plan takes when this agent completes
|
|
41
|
-
# while its partner on the same plan is still running
|
|
42
|
-
Agent = Data.define(:name,
|
|
43
|
-
:description,
|
|
44
|
-
:handles,
|
|
45
|
-
:advances_to,
|
|
46
|
-
:model,
|
|
47
|
-
:allowed_tools,
|
|
48
|
-
:may,
|
|
49
|
-
:network,
|
|
50
|
-
:timeout,
|
|
51
|
-
:prompt,
|
|
52
|
-
:path,
|
|
53
|
-
:ledger,
|
|
54
|
-
:rounds,
|
|
55
|
-
:effort,
|
|
56
|
-
:starts_as,
|
|
57
|
-
:holds_at) do
|
|
58
|
-
def initialize(ledger: [], rounds: 1, effort: nil, starts_as: nil, holds_at: nil, **rest) = super
|
|
59
|
-
|
|
60
|
-
# @return [Boolean] whether this agent changes anything on disk
|
|
61
|
-
def read_only? = advances_to.nil?
|
|
62
|
-
|
|
63
|
-
# @param status [Agentilda::Status]
|
|
64
|
-
# @return [Boolean]
|
|
65
|
-
def handles?(status) = handles.include?(status.key)
|
|
66
|
-
|
|
67
|
-
# The word after the hyphen: `researcher`, `backend`. What the screen's
|
|
68
|
-
# agent column shows, the name being too long for it.
|
|
69
|
-
#
|
|
70
|
-
# @return [String]
|
|
71
|
-
def role = name.split("-", 2).last.to_s
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# More rounds than this costs tokens and buys nothing: an agent that has
|
|
75
|
-
# not finished in five tries is not going to on the sixth.
|
|
76
|
-
Agent::MAX_ROUNDS = 5
|
|
77
|
-
end
|
data/lib/agentilda/board.rb
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Agentilda
|
|
4
|
-
# What the screen draws: everything about the run, as values, refreshed
|
|
5
|
-
# once a second by the dispatcher. The screen paints it and knows nothing
|
|
6
|
-
# about threads; the dispatcher builds it and knows nothing about cells.
|
|
7
|
-
#
|
|
8
|
-
# @!attribute [r] rows
|
|
9
|
-
# @return [Array<Agentilda::Board::Row>] one per agent invocation still
|
|
10
|
-
# worth showing: running, or finished within the last few seconds
|
|
11
|
-
Board = Data.define(:started_at,
|
|
12
|
-
:status,
|
|
13
|
-
:plans,
|
|
14
|
-
:up,
|
|
15
|
-
:down,
|
|
16
|
-
:rows,
|
|
17
|
-
:root,
|
|
18
|
-
:running,
|
|
19
|
-
:live_up,
|
|
20
|
-
:live_down,
|
|
21
|
-
:selected,
|
|
22
|
-
:dialog,
|
|
23
|
-
:help,
|
|
24
|
-
:about,
|
|
25
|
-
:hidden) do
|
|
26
|
-
def initialize(selected: nil, dialog: nil, help: false, about: nil, hidden: 0, **rest) = super
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @!attribute [r] key
|
|
30
|
-
# @return [String] `"001.00/leah-researcher"`, what selection and the
|
|
31
|
-
# keyboard address a row by
|
|
32
|
-
# @!attribute [r] file
|
|
33
|
-
# @return [String] the document holding the agent's latest ledger line
|
|
34
|
-
# @!attribute [r] pr
|
|
35
|
-
# @return [Hash, nil] `{number:, url:, rejected:}` once the plan is at
|
|
36
|
-
# the review stage
|
|
37
|
-
# @!attribute [r] state
|
|
38
|
-
# @return [Symbol] :running, :done, :failed or :stuck
|
|
39
|
-
# @!attribute [r] phase
|
|
40
|
-
# @return [Symbol, nil] the clock's phase while running
|
|
41
|
-
# @!attribute [r] frame
|
|
42
|
-
# @return [Integer] spinner frame counter
|
|
43
|
-
# @!attribute [r] history
|
|
44
|
-
# @return [Array<String>] what the agent has said, newest first, for
|
|
45
|
-
# `run --scroll-height` to show more than the latest line of
|
|
46
|
-
Board::Row = Data.define(:key,
|
|
47
|
-
:at,
|
|
48
|
-
:ordinal,
|
|
49
|
-
:file,
|
|
50
|
-
:agent,
|
|
51
|
-
:role,
|
|
52
|
-
:round,
|
|
53
|
-
:rounds,
|
|
54
|
-
:model,
|
|
55
|
-
:remaining,
|
|
56
|
-
:phase,
|
|
57
|
-
:up,
|
|
58
|
-
:down,
|
|
59
|
-
:message,
|
|
60
|
-
:state,
|
|
61
|
-
:pr,
|
|
62
|
-
:frame,
|
|
63
|
-
:bold,
|
|
64
|
-
:elapsed,
|
|
65
|
-
:subagents,
|
|
66
|
-
:history) do
|
|
67
|
-
def initialize(pr: nil, phase: nil, remaining: nil, frame: 0, bold: false, message: nil,
|
|
68
|
-
elapsed: 0, subagents: 0, history: [], **rest)
|
|
69
|
-
super
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# @param history [Array<String>] newest first
|
|
73
|
-
# @param text [String, nil]
|
|
74
|
-
# @return [Array<String>] +text+ on top, unless it is empty or repeats
|
|
75
|
-
# the newest line: an agent restating its activity is not news
|
|
76
|
-
def self.remember(history, text)
|
|
77
|
-
text = text.to_s.strip
|
|
78
|
-
return history if text.empty? || history.first == text
|
|
79
|
-
|
|
80
|
-
[text, *history].first(Board::Row::HISTORY)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# @return [Boolean]
|
|
84
|
-
def running? = state == :running
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Statuses a row keeps, whatever the screen shows of them. A cap, so a
|
|
88
|
-
# long-running agent's chatter cannot grow without bound. Outside the
|
|
89
|
-
# `Data.define` block, whose constants would land on Agentilda instead.
|
|
90
|
-
Board::Row::HISTORY = 50
|
|
91
|
-
end
|