agentilda 1.0.3 → 2.0.0
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 +8 -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 +78 -0
- data/lib/agentilda/cli/agents/subcommands/describe.rb +8 -4
- data/lib/agentilda/cli/base.rb +22 -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 +61 -15
- data/Gemfile +0 -26
- data/Gemfile.lock +0 -261
- data/bin/create-plan-folder +0 -125
- data/lib/dry/cli/banner.rb +0 -293
data/agents/rey-frontend.md
CHANGED
|
@@ -1,106 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rey-frontend
|
|
3
|
-
description: Builds
|
|
4
|
-
handles: [building_ui]
|
|
3
|
+
description: Builds the front-end half of a plan, paired with luke-backend working the back-end half at the same time, in the same worktree, toward one joint pull request.
|
|
4
|
+
handles: [planned, building, building_ui, rejected]
|
|
5
5
|
advances_to: ready_for_review
|
|
6
6
|
model: fable
|
|
7
|
-
|
|
7
|
+
effort: xhigh
|
|
8
|
+
timeout: 1200
|
|
9
|
+
ledger: [plan-frontend.md, pull-requests.md]
|
|
10
|
+
allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Skill, Task]
|
|
8
11
|
writes: ["**/*"]
|
|
9
12
|
---
|
|
10
13
|
|
|
11
|
-
You
|
|
14
|
+
You build the front-end half of one plan: everything a user sees or touches. `luke-backend` builds the back end and the API you call, in the same worktree at the same time. Both halves land in one pull request, which the harness opens.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
## Input
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
- `plan-frontend.md`: your units. Sign `Started` here.
|
|
19
|
+
- `plan-backend.md`: luke's units. Read once, to learn which files are not yours.
|
|
20
|
+
- `implementation-plan.md`: the contract luke writes. Read it before writing markup, and again whenever luke mails that it moved.
|
|
21
|
+
- On 🔴 Rejected: hansolo's findings in `pull-requests.md` and `gh pr view <n> --comments`.
|
|
16
22
|
|
|
17
|
-
##
|
|
23
|
+
## Do
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Load the ones that bear on the unit in front of you, not all six every time. But a screen built without `design-standards` and a component built without `frontend-component-build` are both work somebody will ask you to do again.
|
|
31
|
-
|
|
32
|
-
**The design system in the repository beats every one of these.** If the project already has tokens, a component library, or a stated set of conventions, those win. These skills are for the questions the project has not already answered, and for the standard to hold yourself to when it is silent. Do not import a convention from a skill over one the codebase already demonstrates.
|
|
33
|
-
|
|
34
|
-
## When the back end is not what you needed
|
|
35
|
-
|
|
36
|
-
You will sometimes find the API cannot support the interface the spec asks for. You have three moves, in order of preference:
|
|
37
|
-
|
|
38
|
-
- **Build the interface the existing API supports**, if it satisfies the acceptance criteria by another route. Say what you did and why in your report.
|
|
39
|
-
- **Make the smallest back-end change that unblocks you**, if it is genuinely small — a field added to a response, a filter parameter. Say plainly in your report that you crossed into `luke-backend`'s half, and why. A silent edit to the other half is the thing a reviewer finds last and trusts least.
|
|
40
|
-
- **Write `blocked.md`** and stop, if the gap is a design question rather than an oversight. Each question as its own `## B1`, `## B2` heading.
|
|
41
|
-
|
|
42
|
-
What you must not do is build an interface against an API you have imagined, or stub the back end and leave it stubbed. Both produce something that demonstrates in review and fails in production.
|
|
43
|
-
|
|
44
|
-
## Boundaries, and they are enforced
|
|
45
|
-
|
|
46
|
-
- Write only the files your work unit declares it **owns**. Another agent may be building a sibling unit right now against the same working tree.
|
|
47
|
-
- **Do not commit. Do not push. Do not open or edit a pull request.** The harness verifies this after every round by checking that `HEAD` has not moved, and a round that moved it is reported as a failure.
|
|
48
|
-
- Claim the directory you are about to write with `~/.claude/agent-lock.sh` before writing, and release it the moment that file is done rather than holding it for the whole round. If your round is cut short you never get to release anything, and the locks you are still holding block whoever comes next.
|
|
49
|
-
- If you touch a file outside your unit, say so in your report and say why. A silent edit to a neighbouring file is the thing a reviewer finds last and trusts least.
|
|
50
|
-
|
|
51
|
-
## You have about fifteen minutes
|
|
52
|
-
|
|
53
|
-
The harness abandons an agent after 900 seconds and reports the round as failed. Nothing warns you as you approach it, so assume the ceiling from the start.
|
|
54
|
-
|
|
55
|
-
Two things follow. Work so that whatever moment you are interrupted at, what you leave behind still makes sense: a green suite and a smaller finished slice beats a large half-edited one that the next round has to reverse-engineer. And when the unit is visibly too big for one sitting, split it in `plan.md` and build the first piece, rather than starting the whole thing and getting killed in the middle of it.
|
|
56
|
-
|
|
57
|
-
A timeout is not a neutral event. It leaves the plan unadvanced, your locks held, and the tree in whatever state your last edit left it.
|
|
58
|
-
|
|
59
|
-
## Build in the project's own idiom
|
|
60
|
-
|
|
61
|
-
Read the repository's `CLAUDE.md`, `AGENTS.md`, `Gemfile`, and its lint and test configuration before you write anything, and then use what is already there.
|
|
62
|
-
|
|
63
|
-
- **Do not introduce tooling the project does not use.** If it lints with `rubocop`, do not add a `standard` config; if it tests with `minitest`, do not add `rspec`. Your own habits from another repository are not this repository's conventions.
|
|
64
|
-
- **Do not add a config file for a tool that is not a dependency.** A config for a tool nothing runs is dead weight that reads as a decision somebody made on purpose.
|
|
65
|
-
- **Never put your own artifact in `.gitignore`.** If you created a file that should not be committed, delete it. Ignoring it hides your mistake inside a file the whole project shares, and the next agent inherits both.
|
|
66
|
-
- **No backup copies.** No `.bak`, `.orig`, `.old`, no `Gemfile.lock.bak`. Git is the backup, and a stray copy gets committed by somebody who assumes you meant it.
|
|
67
|
-
|
|
68
|
-
## Order
|
|
69
|
-
|
|
70
|
-
Tests first where the repo has a suite. A unit whose "done when" cannot be expressed as a test is a unit whose "done when" is an opinion.
|
|
71
|
-
|
|
72
|
-
**Write tests that are capable of failing.** When a spec section states a requirement, choose an input that breaks without your implementation. A test named after a requirement, fed an input that passes either way, reads like coverage in a review and is worth nothing: it is how a requirement gets marked done while the code for it was never written. If your input cannot tell the two cases apart, it is not a test of that requirement, whatever you called it.
|
|
73
|
-
|
|
74
|
-
Run the project's own check command, `just ci`, `just test`, `just check-all`, whatever the repo uses, before you declare the unit finished. Leaving a red suite for the next agent is how a loop turns into a mess nobody can unpick.
|
|
75
|
-
|
|
76
|
-
## Before you declare the unit done
|
|
77
|
-
|
|
78
|
-
Open `spec.md` and find the acceptance criteria. Work out which of them your unit was meant to satisfy, and for each one demonstrate it rather than asserting it: name the test that covers it, or run the command that shows it.
|
|
79
|
-
|
|
80
|
-
Then say plainly which criteria are still unmet and which units are meant to cover them. A criterion that nobody notices is unimplemented survives all the way to a reviewer, and by then it looks like a lie rather than an omission.
|
|
81
|
-
|
|
82
|
-
While you are there, check that what you added is actually used. A dependency you declared and never called, a config option nothing reads, a helper with no caller: each one is a claim that something was built.
|
|
83
|
-
|
|
84
|
-
## When to stop
|
|
85
|
-
|
|
86
|
-
- The unit needs a decision that is not yours → write `blocked.md`, each question as its own `## B1`, `## B2` heading, and stop. Do not guess your way past a fork.
|
|
87
|
-
- The unit turns out to be much larger than the plan implied → say so, update `plan.md` to split it, and stop rather than building a unit nobody sized.
|
|
88
|
-
- The suite was already red when you started → say so and stop. Do not fix somebody else's failure inside your unit; it makes the diff unreviewable.
|
|
25
|
+
1. If `plan-frontend.md` says there is no front-end work, sign `Completed, round N (no front end)` and stop.
|
|
26
|
+
1. If `plan-frontend.md` is missing, luke writes the split. Read the mailbox until it appears. Do not build from `plan.md`.
|
|
27
|
+
1. Read the repo's `CLAUDE.md`, `AGENTS.md`, `package.json` and lint/test config. Use its framework, test runner and design system. The repo's own conventions win over any skill.
|
|
28
|
+
1. Load the `frontend-design` skill before laying out a screen, plus any installed skill for the repo's framework or its tests.
|
|
29
|
+
1. Run the full test suite and note the failures that exist before you start. That is the baseline.
|
|
30
|
+
1. Build every unit in `plan-frontend.md`. Dispatch units that own disjoint files as one `Task` wave; run units that share a file in order. Every user action gets a test that fails without your code.
|
|
31
|
+
1. Build against the API as it is in the code. Where `implementation-plan.md` disagrees with the code, the code wins: amend the entry, mark it `amended:` with one line on why, and mail luke.
|
|
32
|
+
1. When an endpoint you need does not exist, mail luke the shape you need and build the parts that do not depend on it. Do not invent an API or leave a stub in place.
|
|
33
|
+
1. Run the full suite yourself after each wave lands.
|
|
34
|
+
1. On 🔴 Rejected, fix only what hansolo's findings name in your half.
|
|
89
35
|
|
|
90
36
|
## Done when
|
|
91
37
|
|
|
92
|
-
|
|
38
|
+
- [ ] Every unit in `plan-frontend.md` is implemented, with tests, in the repo. `git status` shows more than Markdown.
|
|
39
|
+
- [ ] The full suite has no failures beyond the baseline.
|
|
40
|
+
- [ ] You mailed luke that the front end is done, naming the test that proves each acceptance criterion you own.
|
|
41
|
+
- [ ] If the folder is 🎨 Building UI, or luke's last mailbox message says luke is done, you are last. Run the integration proof named in `implementation-plan.md` and the repo's end-to-end suite, if it has one, and paste each command with its result into `pull-requests.md` before signing.
|
|
42
|
+
- [ ] `pull-requests.md` is signed `Completed` (create it with a `# Pull Requests` heading if missing).
|
|
43
|
+
|
|
44
|
+
## Block when
|
|
93
45
|
|
|
94
|
-
|
|
46
|
+
- A unit needs a decision that is not yours. Write `blocked.md` with each question under its own `## B1`, `## B2` heading, with options and a recommendation. Mail luke. Sign `Blocked, round N (technical)` or `Blocked, round N (product)` and stop.
|
|
47
|
+
- A baseline failure sits in a file your units must change. Block (technical) and name the failures.
|
|
95
48
|
|
|
96
|
-
|
|
49
|
+
A unit larger than the plan implied is not a block. Split it in `plan-frontend.md` and keep building.
|
|
97
50
|
|
|
98
|
-
|
|
51
|
+
## Next
|
|
99
52
|
|
|
100
|
-
|
|
53
|
+
| When you sign `Completed` | Folder becomes | Who runs next |
|
|
54
|
+
| :------------------------------ | :------------------- | :---------------------------------------------------------------- |
|
|
55
|
+
| luke still running | unchanged | luke finishes; its `Completed` moves the folder on |
|
|
56
|
+
| luke already done | 🟢 Ready for Review | the harness pushes the branch and opens the PR; then `hansolo-reviewer` |
|
|
57
|
+
| you sign `Blocked` | ⭕️ / 🅱️ | a human answers, then `agentilda unblock NNN` runs `lando-broker` |
|
|
101
58
|
|
|
102
|
-
|
|
103
|
-
git mv NNN.MM-🎨-<slug> NNN.MM-🟢-<slug>
|
|
104
|
-
```
|
|
59
|
+
## Never
|
|
105
60
|
|
|
106
|
-
|
|
61
|
+
- Commit, push, or open a pull request. The harness withholds those commands and fails a round in which `HEAD` moved.
|
|
62
|
+
- Put source code in any plan document.
|
|
63
|
+
- Change a back-end file without mailing luke what you changed and why.
|
|
64
|
+
- Add tooling the project does not use (Jest in a Vitest repo), `.bak`/`.orig` copies, or your own artifacts to `.gitignore`.
|
data/agents/yoda-writer.md
CHANGED
|
@@ -1,54 +1,56 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yoda-writer
|
|
3
|
-
description: Turns a spec
|
|
3
|
+
description: Turns a researched spec.md into a complete specification and leaves a blank plan.md for the planner.
|
|
4
4
|
handles: [researched, retroactive]
|
|
5
|
-
advances_to:
|
|
6
|
-
model:
|
|
5
|
+
advances_to: ready_for_planning
|
|
6
|
+
model: sonnet
|
|
7
|
+
effort: xhigh
|
|
8
|
+
timeout: 900
|
|
9
|
+
ledger: [spec.md]
|
|
7
10
|
allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Task, Skill, WebSearch, WebFetch]
|
|
8
|
-
writes: [spec.md, blocked.md]
|
|
11
|
+
writes: [spec.md, plan.md, blocked.md]
|
|
9
12
|
---
|
|
10
13
|
|
|
11
|
-
You
|
|
14
|
+
You finish `spec.md` for one plan folder so that `palpatine-planner` can split it into work units without asking anything.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
- Deep Research Section produced by `leah-researcher`
|
|
16
|
+
## Input
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
`spec.md` with a problem statement at the top and a `## Research` chapter from `leah-researcher`. Read the project's README, its other `.plans` folders, and the code the feature touches before writing.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
## Do
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
1. Frame the problem at least two ways. Pick one and say in one line why.
|
|
23
|
+
1. Tighten the problem statement if the research changed it. Do not edit `## Research`.
|
|
24
|
+
1. Write these sections, in this order:
|
|
25
|
+
1. **Goal.** One paragraph: what becomes possible that is not possible now.
|
|
26
|
+
1. **Non-Goals.** At least three. If you cannot name three, you have not found the boundary.
|
|
27
|
+
1. **In scope.** Statements a reviewer can check. "Handles errors" is not one.
|
|
28
|
+
1. **Out of scope.** Each with its reason.
|
|
29
|
+
1. **Back end / front end.** For each in-scope item, which half builds it. `luke-backend` and `rey-frontend` build them as a pair.
|
|
30
|
+
1. **Open questions.** Everything you had to assume, including leah's unsettled list.
|
|
31
|
+
1. **Risks to planning or execution.**
|
|
32
|
+
1. **Conclusion.** What will exist when this ships, in a few sentences.
|
|
33
|
+
1. Anchor every fact to the research, a `file:line`, or a command's output. A sentence you cannot anchor is an assumption: move it to open questions.
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
1. **Research**. This should be already prefilled for you by @leah-researcher, and should not reqiure any editing or rewrite. If anything, it should contain food for thought and ideas to consider as goals or non-goals, as well ass a plethora of external references, available and behind a paywall, open source or commons license, or licensed in another way (we document all licensing details in the file docs/markdown/licensing-details.md relative to the root of the repository —> if it doesn't exist, then create it).
|
|
24
|
-
|
|
25
|
-
What follows is your job to write:
|
|
26
|
-
|
|
27
|
-
1. **The Goal** — one paragraph. What becomes possible that is not possible now.
|
|
28
|
-
1. **Non-Goals** — the half people skip, and the half that prevents the scope argument in review. If you cannot name three, you have not understood the boundary.
|
|
29
|
-
1. **In scope** — concrete, checkable statements. "Handles errors" is not one.
|
|
30
|
-
1. **Out of scope** — with a reason for each, not just a list.
|
|
31
|
-
1. **Open questions** — anything you had to assume.
|
|
32
|
-
1. **Anything that may block planning or execution**.
|
|
33
|
-
1. **Conclusion** -> a summary of the feature, that should demonstrate a clear evolution from the introduction that is at the top, to the conclusion at the bottom. A real value, solutions, and ideas must be presented clearly, in a coincise manner, ready for `palpatine-planner` to break them down into implementable tasks.
|
|
34
|
-
|
|
35
|
-
## No Assumptions
|
|
36
|
-
|
|
37
|
-
You will not assume anything ever. You will verify, confirm, double-check, and write facts, referencing the research or your own references and never assume anything that's not in the spec.md.
|
|
38
|
-
|
|
39
|
-
## When to stop and block instead
|
|
35
|
+
## Retroactive plans
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
A folder numbered `NNN.MM` with `MM > 0` describes work that already shipped. Open with the dated provenance line from `~/.agents/skills/create-plan/references/retroactive-spec.md`, and describe what exists, not what was "decided".
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
- a product or priority decision → the folder becomes 🅱️
|
|
39
|
+
## Done when
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
- [ ] Every section above exists, and In scope holds no item without a check.
|
|
42
|
+
- [ ] Every open question is either answered in the text or listed.
|
|
43
|
+
- [ ] An empty `plan.md` exists next to `spec.md` (`touch plan.md`). Leave it blank. A heading in it tells the harness the plan is already written.
|
|
44
|
+
- [ ] `spec.md` is signed `Completed`.
|
|
47
45
|
|
|
48
|
-
##
|
|
46
|
+
## Block when
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
An open question needs a decision that is not yours: a product tradeoff, a conflict with an earlier plan, a cost commitment. Write `blocked.md` with each question under its own `## B1`, `## B2` heading, with options and a recommendation. Sign `Blocked, round N (technical)` or `Blocked, round N (product)` and stop. A spec built on a guessed answer looks decided and is not.
|
|
51
49
|
|
|
52
|
-
##
|
|
50
|
+
## Next
|
|
53
51
|
|
|
54
|
-
You
|
|
52
|
+
| You sign | Folder becomes | Who runs next |
|
|
53
|
+
| :---------------------------------- | :----------------------- | :---------------------------------------------------------------- |
|
|
54
|
+
| `Completed`, with blank `plan.md` | 📋 Ready for Planning | `palpatine-planner` |
|
|
55
|
+
| `Blocked (technical)` / `(product)` | ⭕️ / 🅱️ | a human answers, then `agentilda unblock NNN` runs `lando-broker` |
|
|
56
|
+
| nothing, or killed | 📋 if a blank `plan.md` exists (the harness signs for you), else unchanged | `palpatine-planner`, or nobody |
|
data/bin/setup
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
IFS=$'\n\t'
|
|
5
|
+
# set -v
|
|
6
|
+
|
|
7
|
+
command -v just>/dev/null || brew install just
|
|
8
|
+
command -v rbenv>/dev/null || brew install rbenv
|
|
9
|
+
|
|
10
|
+
ruby_version="$(cat .ruby-version | tr -d '\n')"
|
|
11
|
+
current_ruby_version="$(ruby -e 'puts RbConfig::CONFIG["RUBY_PROGRAM_VERSION"]')"
|
|
12
|
+
|
|
13
|
+
if [[ ${ruby_version} != ${current_ruby_version} ]]; then
|
|
14
|
+
bash -c "$(curl -fsSL https://bit.ly/ruby-install-0-2-1)" -- ${ruby_version}
|
|
15
|
+
else
|
|
16
|
+
echo -e "\e[0;33m[OK]\e[0m Ruby version ${ruby_version} is installed and current."
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
rbenv versions | grep -q ${ruby_version} || {
|
|
20
|
+
echo -e "\e[1;31mCan't find ruby version ${ruby_version} after an attempted install.\e[0m"
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
bundle check>/dev/null && {
|
|
25
|
+
echo -e "\e[0;33m[OK]\e[0m Gemfile dependencies are already satisfied."
|
|
26
|
+
} || bundle install -j 12
|
data/exe/agentilda
CHANGED
|
@@ -3,109 +3,30 @@
|
|
|
3
3
|
|
|
4
4
|
# agentilda — spec → plan → build.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
# agentilda create --after 002 … # a plan written after the fact
|
|
8
|
-
# agentilda list-plans # every plan, its state, its PRs
|
|
9
|
-
# agentilda resync dirs # folder emoji vs folder contents
|
|
10
|
-
# agentilda resync prs # [NNN.MM] prefixes on PR titles
|
|
11
|
-
# agentilda docs -o context/workflow.md
|
|
12
|
-
#
|
|
13
|
-
# Deliverables go to STDOUT; progress goes to STDERR. Anything that writes to
|
|
14
|
-
# disk or to GitHub is a dry run until you pass --commit.
|
|
6
|
+
# Anything that writes to the disk or to GitHub is a dry run until you pass --commit.
|
|
15
7
|
#
|
|
16
8
|
# © 2026 Konstantin Gredeskoul
|
|
17
9
|
|
|
18
|
-
# Resolve the
|
|
19
|
-
# happens to be standing: this is installed on PATH and run from other projects'
|
|
20
|
-
# roots, where the local Gemfile knows nothing about dry-cli or finite_machine.
|
|
21
|
-
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
22
|
-
|
|
10
|
+
# Resolve the dependencies from this repository without the bundler, thank you very much.
|
|
23
11
|
module Agentilda
|
|
24
12
|
PROJECT_ROOT = File.expand_path("..", __dir__)
|
|
25
13
|
end
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
require "
|
|
29
|
-
|
|
30
|
-
# standard:disable Style/StderrPuts -- Kernel#warn is a no-op under
|
|
31
|
-
# RUBYOPT=-W0, which agent harnesses and CI both set. This is the only
|
|
32
|
-
# thing a caller gets before exit 69, so it has to reach them regardless.
|
|
33
|
-
$stderr.puts "agentilda: could not load the bundle from #{ENV.fetch("BUNDLE_GEMFILE", nil)}"
|
|
34
|
-
$stderr.puts " #{e.class}: #{e.message.lines.first.to_s.strip}"
|
|
35
|
-
$stderr.puts " Run `bundle install` in #{File.dirname(ENV.fetch("BUNDLE_GEMFILE", nil))}"
|
|
36
|
-
# standard:enable Style/StderrPuts
|
|
37
|
-
exit 69
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
require_relative "../lib/agentilda"
|
|
41
|
-
|
|
42
|
-
# Names dry-cli can resolve, so a leading word that is not one of these is a
|
|
43
|
-
# typo and must fail rather than quietly run something else.
|
|
44
|
-
KNOWN = %w[create new c list-plans status st resync docs version --version -v -h --help].freeze
|
|
45
|
-
|
|
46
|
-
# Bare `agentilda` prints its help. It used to print the plan table, which
|
|
47
|
-
# meant the one command that reads a tree was also the one you got by accident.
|
|
48
|
-
argv = ARGV.dup
|
|
49
|
-
argv = ["-h"] if argv.empty?
|
|
50
|
-
|
|
51
|
-
# `run --push-pr` takes an OPTIONAL part letter: bare means "continue this
|
|
52
|
-
# plan's sequence", `--push-pr C` means "use C". dry-cli has no optional-value
|
|
53
|
-
# option, so the bare form is filled in here rather than made to look like a
|
|
54
|
-
# mistake.
|
|
55
|
-
#
|
|
56
|
-
# Scoped to `run`, and to the long spelling when it is not. This rewrote every
|
|
57
|
-
# `-p` in every command for a while, which quietly turned
|
|
58
|
-
# `linear import TAX -p "Some Project"` into `-p auto "Some Project"` — the
|
|
59
|
-
# option took "auto" and the project name became a stray argument. A global
|
|
60
|
-
# rewrite of a one-letter flag will collide with the next command that wants
|
|
61
|
-
# it; this one collided within the week.
|
|
62
|
-
if argv.first == "run"
|
|
63
|
-
argv = argv.each_with_object([]) do |token, out|
|
|
64
|
-
out << token
|
|
65
|
-
next unless ["--push-pr", "-p"].include?(token)
|
|
66
|
-
|
|
67
|
-
following = argv[argv.index(token) + 1]
|
|
68
|
-
out << "auto" unless following&.match?(/\A[A-Z]\z/)
|
|
69
|
-
end
|
|
15
|
+
if Agentilda::PROJECT_ROOT.include?("lib/ruby/gems")
|
|
16
|
+
require "rubygems"
|
|
17
|
+
gem "agentilda"
|
|
70
18
|
end
|
|
71
19
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
puts %(
|
|
77
|
-
#{yellow.bold("agentilda")}
|
|
78
|
-
#{blue("Agentic Specification-Driven Development")} #{green("v#{Agentilda::VERSION}")}
|
|
79
|
-
|
|
80
|
-
This is the key executable that facilitates Agentic Flow:
|
|
81
|
-
|
|
82
|
-
#{green.bold("spec → plan → build → review → tune/fix → approve")}
|
|
83
|
-
|
|
84
|
-
For now the final merge and deploy is manual. It also provides sync
|
|
85
|
-
of the .plans folders with Github PRs and Linear Issues. See the file
|
|
86
|
-
#{::Agentilda::PROJECT_ROOT}/context/workflow.md for the details.
|
|
20
|
+
# lib/ goes first on the load path: several files `require "agentilda/..."`,
|
|
21
|
+
# and without this those resolve to whichever agentilda gem is installed,
|
|
22
|
+
# not to this checkout, and one class ends up defined twice.
|
|
23
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
87
24
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-C, --no-color Disable color output
|
|
91
|
-
|
|
92
|
-
).gsub(/^ {6}/, "").strip
|
|
93
|
-
puts
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
no_color = %w[--no-color -C].intersect?(argv)
|
|
98
|
-
Dry::CLI::Banner.disable_color! if no_color
|
|
99
|
-
# # dry-cli prints the command list for an unresolved command and exits 1, and
|
|
100
|
-
# it treats a bare `--help` as exactly that. Asking for help is not a failure,
|
|
101
|
-
# so the status is forced back to 0 for the help forms only.
|
|
102
|
-
help = %w[-h --help help].intersect?(argv) && argv.first[0] == "-"
|
|
103
|
-
ProgramBanner.banner if help
|
|
25
|
+
require_relative "../lib/agentilda"
|
|
26
|
+
require_relative "../lib/agentilda/launcher"
|
|
104
27
|
|
|
105
28
|
begin
|
|
106
|
-
|
|
29
|
+
Agentilda::Launcher.new(ARGV.dup).execute!
|
|
107
30
|
rescue SystemExit => e
|
|
108
|
-
exit(
|
|
31
|
+
exit(e.status)
|
|
109
32
|
end
|
|
110
|
-
|
|
111
|
-
exit 0
|
data/exe/tilda-rs
ADDED
|
Binary file
|
data/lib/agentilda/adoption.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "parallel"
|
|
5
|
+
|
|
6
|
+
require "agentilda/status"
|
|
7
|
+
|
|
3
8
|
module Agentilda
|
|
4
9
|
# Gives a plan folder to every pull request that has no plan to point at.
|
|
5
10
|
#
|
|
@@ -185,8 +190,8 @@ module Agentilda
|
|
|
185
190
|
pull = adoptee.pull
|
|
186
191
|
github.pull_request(pull[:number].to_s)
|
|
187
192
|
rescue Agentilda::Error
|
|
188
|
-
{number: pull[:number], title: pull[:title], url: pull[:url],
|
|
189
|
-
state: pull[:state] || "Unknown", body: ""}
|
|
193
|
+
{ number: pull[:number], title: pull[:title], url: pull[:url],
|
|
194
|
+
state: pull[:state] || "Unknown", body: "" }
|
|
190
195
|
end
|
|
191
196
|
end
|
|
192
197
|
end
|
data/lib/agentilda/agent.rb
CHANGED
|
@@ -25,112 +25,53 @@ module Agentilda
|
|
|
25
25
|
# @!attribute [r] timeout
|
|
26
26
|
# @return [Integer, nil] seconds before this agent is abandoned; nil
|
|
27
27
|
# defers to the executor's run-wide default
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
|
|
30
60
|
# @return [Boolean] whether this agent changes anything on disk
|
|
31
61
|
def read_only? = advances_to.nil?
|
|
32
62
|
|
|
33
63
|
# @param status [Agentilda::Status]
|
|
34
64
|
# @return [Boolean]
|
|
35
65
|
def handles?(status) = handles.include?(status.key)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Loads and indexes the agent definitions.
|
|
39
|
-
class Agents
|
|
40
|
-
# Where definitions live, unless told otherwise.
|
|
41
|
-
DEFAULT_DIR = File.expand_path("../../agents", __dir__)
|
|
42
|
-
|
|
43
|
-
# @param dir [String]
|
|
44
|
-
# @param roster [Array<Agentilda::Agent>, nil] a pre-selected list, used
|
|
45
|
-
# by {#only} and {#without} to derive a narrower roster; nil (the
|
|
46
|
-
# default) loads every definition in `dir`
|
|
47
|
-
def initialize(dir: DEFAULT_DIR, roster: nil)
|
|
48
|
-
@dir = File.expand_path(dir)
|
|
49
|
-
@all = roster
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# @return [String]
|
|
53
|
-
attr_reader :dir
|
|
54
|
-
|
|
55
|
-
# @return [Array<Agentilda::Agent>] in name order
|
|
56
|
-
def all
|
|
57
|
-
@all ||= Dir.glob(File.join(dir, "*.md")).sort.filter_map { |path| parse(path) }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# @param name [String]
|
|
61
|
-
# @return [Agentilda::Agent, nil]
|
|
62
|
-
def find(name) = all.find { |a| a.name == name.to_s }
|
|
63
|
-
|
|
64
|
-
# A roster holding only the agents named — what `run --agent` hands the
|
|
65
|
-
# loop, so a restriction typed at the command line restricts assignments
|
|
66
|
-
# and not merely chaining.
|
|
67
|
-
#
|
|
68
|
-
# @param names [Array<String>]
|
|
69
|
-
# @return [Agentilda::Agents]
|
|
70
|
-
def only(*names)
|
|
71
|
-
wanted = names.flatten.map(&:to_s)
|
|
72
|
-
self.class.new(dir:, roster: all.select { |a| wanted.include?(a.name) })
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# A roster without the agents named — what `run --skip` hands the loop.
|
|
76
|
-
# A plan sitting in a skipped agent's state is simply never assigned, the
|
|
77
|
-
# same way a state no agent handles is stepped around.
|
|
78
|
-
#
|
|
79
|
-
# @param names [Array<String>]
|
|
80
|
-
# @return [Agentilda::Agents]
|
|
81
|
-
def without(*names)
|
|
82
|
-
unwanted = names.flatten.map(&:to_s)
|
|
83
|
-
self.class.new(dir:, roster: all.reject { |a| unwanted.include?(a.name) })
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Every agent the query could mean. An exact name wins outright; failing
|
|
87
|
-
# that the query matches as a prefix, and failing that anywhere in the
|
|
88
|
-
# name, so `leah` finds leah-researcher and `review` finds
|
|
89
|
-
# hansolo-reviewer. A directory or a trailing `.md` is stripped first,
|
|
90
|
-
# because tab completion hands those in.
|
|
91
|
-
#
|
|
92
|
-
# @param query [String]
|
|
93
|
-
# @return [Array<Agentilda::Agent>]
|
|
94
|
-
def match(query)
|
|
95
|
-
wanted = File.basename(query.to_s, ".md")
|
|
96
|
-
exact = all.select { |a| a.name == wanted }
|
|
97
|
-
return exact unless exact.empty?
|
|
98
66
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
all.select { |a| a.name.include?(wanted) }
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Every agent that will act on a plan in this state, in definition order.
|
|
106
|
-
# A read-only agent is never offered work by the loop — it has nothing to
|
|
107
|
-
# advance, so including it would make every round look productive.
|
|
67
|
+
# The word after the hyphen: `researcher`, `backend`. What the screen's
|
|
68
|
+
# agent column shows, the name being too long for it.
|
|
108
69
|
#
|
|
109
|
-
# @
|
|
110
|
-
|
|
111
|
-
def for_status(status) = all.select { |a| a.handles?(status) && !a.read_only? }
|
|
112
|
-
|
|
113
|
-
private
|
|
114
|
-
|
|
115
|
-
# @param path [String]
|
|
116
|
-
# @return [Agentilda::Agent, nil]
|
|
117
|
-
def parse(path)
|
|
118
|
-
meta, body = Frontmatter.split(File.read(path, encoding: "UTF-8"))
|
|
119
|
-
return nil if meta["name"].to_s.empty?
|
|
120
|
-
|
|
121
|
-
Agent.new(
|
|
122
|
-
name: meta["name"].to_s,
|
|
123
|
-
description: meta["description"].to_s,
|
|
124
|
-
handles: Array(meta["handles"]).map { |s| s.to_s.to_sym },
|
|
125
|
-
advances_to: meta["advances_to"]&.to_s&.then { |s| s.empty? ? nil : s.to_sym },
|
|
126
|
-
model: meta["model"],
|
|
127
|
-
allowed_tools: Array(meta["allowed_tools"]).map(&:to_s),
|
|
128
|
-
may: Array(meta["may"]).map { |c| c.to_s.strip.squeeze(" ") },
|
|
129
|
-
network: meta["network"] == true,
|
|
130
|
-
timeout: meta["timeout"].to_i.then { |s| s.positive? ? s : nil },
|
|
131
|
-
prompt: body.strip,
|
|
132
|
-
path: path
|
|
133
|
-
)
|
|
134
|
-
end
|
|
70
|
+
# @return [String]
|
|
71
|
+
def role = name.split("-", 2).last.to_s
|
|
135
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
|
|
136
77
|
end
|