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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/agentilda.gemspec +10 -6
  3. data/agents/hansolo-reviewer.md +42 -13
  4. data/agents/lando-broker.md +40 -46
  5. data/agents/leah-researcher.md +36 -58
  6. data/agents/luke-backend.md +49 -61
  7. data/agents/palpatine-planner.md +39 -21
  8. data/agents/rey-frontend.md +44 -86
  9. data/agents/yoda-writer.md +38 -36
  10. data/bin/setup +26 -0
  11. data/exe/agentilda +13 -92
  12. data/exe/tilda-rs +0 -0
  13. data/lib/agentilda/adoption.rb +7 -2
  14. data/lib/agentilda/agent.rb +40 -99
  15. data/lib/agentilda/agents.rb +122 -0
  16. data/lib/agentilda/board.rb +91 -0
  17. data/lib/agentilda/brief.rb +54 -5
  18. data/lib/agentilda/child.rb +92 -0
  19. data/lib/agentilda/cli/agents/subcommands/describe.rb +8 -4
  20. data/lib/agentilda/cli/base.rb +46 -6
  21. data/lib/agentilda/cli/create/create.rb +46 -19
  22. data/lib/agentilda/cli/docs/docs.rb +6 -2
  23. data/lib/agentilda/cli/index/index.rb +6 -4
  24. data/lib/agentilda/cli/linear/linear.rb +3 -2
  25. data/lib/agentilda/cli/linear/subcommands/import.rb +26 -15
  26. data/lib/agentilda/cli/mail/mail.rb +31 -0
  27. data/lib/agentilda/cli/mail/subcommands/read.rb +43 -0
  28. data/lib/agentilda/cli/mail/subcommands/send.rb +43 -0
  29. data/lib/agentilda/cli/resync/subcommands/dirs.rb +6 -4
  30. data/lib/agentilda/cli/resync/subcommands/prs.rb +21 -13
  31. data/lib/agentilda/cli/run/run.rb +207 -99
  32. data/lib/agentilda/cli/unblock/unblock.rb +24 -13
  33. data/lib/agentilda/cli/worktree/worktree.rb +85 -0
  34. data/lib/agentilda/cli.rb +43 -0
  35. data/lib/agentilda/clock.rb +114 -0
  36. data/lib/agentilda/console.rb +157 -0
  37. data/lib/agentilda/control.rb +63 -0
  38. data/lib/agentilda/creator.rb +7 -2
  39. data/lib/agentilda/dashboard.rb +219 -0
  40. data/lib/agentilda/dev_work.rb +2 -0
  41. data/lib/agentilda/diagram.rb +12 -7
  42. data/lib/agentilda/dispatcher.rb +646 -0
  43. data/lib/agentilda/documentation.rb +14 -6
  44. data/lib/agentilda/executor.rb +362 -204
  45. data/lib/agentilda/feature.rb +14 -135
  46. data/lib/agentilda/frontmatter.rb +4 -2
  47. data/lib/agentilda/github.rb +19 -11
  48. data/lib/agentilda/index.rb +11 -11
  49. data/lib/agentilda/keyboard.rb +76 -8
  50. data/lib/agentilda/launcher.rb +69 -0
  51. data/lib/agentilda/ledger.rb +233 -0
  52. data/lib/agentilda/linear/api.rb +14 -10
  53. data/lib/agentilda/linear/attribution.rb +5 -5
  54. data/lib/agentilda/linear/import.rb +30 -14
  55. data/lib/agentilda/linear/issue.rb +6 -3
  56. data/lib/agentilda/linear/mapping.rb +18 -17
  57. data/lib/agentilda/linear/push.rb +19 -12
  58. data/lib/agentilda/linear/survey.rb +3 -2
  59. data/lib/agentilda/linear/unit.rb +12 -12
  60. data/lib/agentilda/linear.rb +5 -6
  61. data/lib/agentilda/mailbox.rb +141 -0
  62. data/lib/agentilda/markdown.rb +1 -1
  63. data/lib/agentilda/progress_log.rb +5 -1
  64. data/lib/agentilda/publisher.rb +6 -3
  65. data/lib/agentilda/pull_request.rb +0 -186
  66. data/lib/agentilda/pull_requests.rb +230 -0
  67. data/lib/agentilda/reporter.rb +9 -3
  68. data/lib/agentilda/resync.rb +120 -52
  69. data/lib/agentilda/roster.rb +31 -48
  70. data/lib/agentilda/runner.rb +72 -389
  71. data/lib/agentilda/screen/ratatui/bar.rb +57 -0
  72. data/lib/agentilda/screen/ratatui/key_translator.rb +29 -0
  73. data/lib/agentilda/screen/ratatui.rb +318 -0
  74. data/lib/agentilda/state_file.rb +174 -0
  75. data/lib/agentilda/state_machine.rb +60 -32
  76. data/lib/agentilda/status.rb +120 -37
  77. data/lib/agentilda/subject.rb +133 -0
  78. data/lib/agentilda/tally.rb +17 -11
  79. data/lib/agentilda/transcript.rb +47 -16
  80. data/lib/agentilda/tree.rb +10 -6
  81. data/lib/agentilda/ui.rb +204 -190
  82. data/lib/agentilda/unblocker.rb +25 -11
  83. data/lib/agentilda/version.rb +1 -1
  84. data/lib/agentilda/worktree.rb +14 -18
  85. data/lib/agentilda.rb +57 -70
  86. metadata +65 -13
  87. data/Gemfile +0 -26
  88. data/Gemfile.lock +0 -261
  89. data/bin/create-plan-folder +0 -125
  90. data/lib/dry/cli/banner.rb +0 -293
@@ -1,106 +1,64 @@
1
1
  ---
2
2
  name: rey-frontend
3
- description: Builds one front-end work unit against the back end luke-backend already landed components, views, and the wiring between them with tests, and without committing.
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
- allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Skill]
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 are implementing **one** front-end work unit from `plan.md`. You have been given the plan folder and the unit to build.
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
- `luke-backend` has already built the back end in this same working tree, and its report names the endpoints it landed. Read them, and read the code behind them: the API as it exists is your contract, not the API as `spec.md` imagined it. Where the two disagree, the code is what ships and the spec is what somebody hoped for.
16
+ ## Input
14
17
 
15
- If `plan.md` labels its units by discipline, build only the front-end ones. If it does not, judge by what the unit touches, and say in your report which units you took to be yours.
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
- ## Load the design skills before you write markup
23
+ ## Do
18
24
 
19
- You have the `Skill` tool, and you are the only implementer who does. Use it. These are installed and each one is worth more than your instinct about what good looks like:
20
-
21
- | Skill | Load it when |
22
- | :------------------------- | :------------------------------------------------------------------------------------------------------------ |
23
- | `design-standards` | Before laying out any page or screen. Production-grade standards for spacing, type, hierarchy and state. |
24
- | `design-system` | Whenever a token, a variant or a component boundary is in question, or you are tempted to invent a one-off. |
25
- | `frontend-component-build` | Before building a component: accessible markup, sensible props, defined states, tested behaviour. |
26
- | `frontend-design` | For visual judgement on a screen as a whole rather than a component in isolation. |
27
- | `accessibility-audit` | Before you call a unit done, and any time a control is not a native element. |
28
- | `web-design-guidelines` | To review what you built against the Web Interface Guidelines. Forty lines; read it at the end of every unit. |
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
- The unit's "done when" holds, the suite is green, the acceptance criteria you were responsible for are demonstrated, and the working tree contains your changes **uncommitted**, ready for a human to read.
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
- ## When there is no front-end work left
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
- Check `plan.md` for another front-end work unit that is not yet done. If one remains, stop here — leave the plan folder named Building UI, exactly as you found it. Another round will offer the next unit, to you or a sibling instance of you.
49
+ A unit larger than the plan implied is not a block. Split it in `plan-frontend.md` and keep building.
97
50
 
98
- **If the plan has no front-end work at all, that is a normal outcome, not a problem.** Plenty of plans are entirely back end. Say so in your report, build nothing, and advance the folder exactly as below. Do not invent an interface nobody asked for so that this state has something to show for itself.
51
+ ## Next
99
52
 
100
- If yours was the last unit, you decide the plan is ready for review, not the harness — that is why the harness never guesses it from a dirty working tree. Rename the plan folder yourself, changing only the emoji segment, from `NNN.MM-🎨-<slug>` to `NNN.MM-🟢-<slug>`:
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
- Run it from the plan folder's parent directory, with the plan folder path you were given above. Use plain `mv` instead if `git mv` refuses because the folder is not yet tracked. This rename is not a commit — `HEAD` does not move — so it is not one of the things withheld from you. Do it last, after everything else is finished and the suite is green: it is what tells the harness to stage, commit, push and open the pull request for everything both halves built.
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`.
@@ -1,54 +1,56 @@
1
1
  ---
2
2
  name: yoda-writer
3
- description: Turns a spec in the ".plans" folder into a detailed completed specifications based on research, brainstorming, trying out various schemes and ideas.
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: planned
6
- model: fable
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 are writing or rewriting the actual innovative part of the `spec.md` file for a single plan folder. You are basing this on two pillars that should already be there for 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
- - Introduction Section
14
- - Deep Research Section produced by `leah-researcher`
16
+ ## Input
15
17
 
16
- Before drafting, invoke the `superpowers:brainstorming` skill (via the Skill tool) to explore more than one way to frame the problem before committing to one. Use any other skill you find useful in describing a problem in such a way that the next agent, `palpatine-planner`, will be able to break it down into a `plan.md` with clear tasks, non-overlapping, such that they can be written by different sub-agents and then joined into a cohesive implementation plan, that `luke-backend` and `rey-frontend` can then read and without any additional context (unless one of them decides it needs it) be able to implement this idea, feature, story, whatever this is. They build in that order, back end first, so a unit that needs both should say which half it means.
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
- Read the surrounding project first — its README, its existing `.plans` entries, and the code the feature will touch. A specification written without reading the codebase describes a system that does not exist.
20
+ ## Do
19
21
 
20
- ## What the final `spec.md` document should contain
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
- 1. **Problem Statement**. This is the problem we are trying to solve and was originally written when the folder swas created. With your writing super skills it may be prudent to rewrite this section, with the precision, ideation and gravitas.
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
- If answering an open question requires a decision that is not yours a product tradeoff, a contradiction with an earlier plan, a cost commitment — **do not guess**. Write `blocked.md` instead, with each question as its own `## B1`, `## B2` heading, each carrying options and a recommendation, and say which kind of block it is:
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
- - an engineering or architecture decision → the folder becomes ⭕️
44
- - a product or priority decision → the folder becomes 🅱️
39
+ ## Done when
45
40
 
46
- A specification built on a guessed answer is worse than no specification, because it looks decided.
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
- ## Retroactive plans
46
+ ## Block when
49
47
 
50
- If the folder's number has a non-zero decimal (`NNN.MM` where MM > 0), the work already shipped. Open the document with the dated provenance line see `~/.agents/skills/create-plan/references/retroactive-spec.md`. Describe what exists. Do not write it as though it were decided in advance.
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
- ## Done when
50
+ ## Next
53
51
 
54
- You stop writing the spec when it's clear as day what we are building and what this spec specifically does not cover. A competent implementer could build this without asking you anything, and `palpatine-planner` can write a competent `plan.md` without asking any questions.
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
- # agentilda create tax rule dsl # the next numbered plan folder
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 bundle from this repository rather than from wherever the caller
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
- begin
28
- require "bundler/setup"
29
- rescue LoadError, StandardError => e
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
- module ProgramBanner
73
- extend Dry::CLI::Banner::ColorMethods
74
-
75
- def self.banner
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
- #{yellow.bold("GLOBAL FLAGS")}
89
- -h, --help Show this help message and exit
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
- Dry::CLI.new(Agentilda::CLI).call(arguments: help ? [] : argv) || 0
29
+ Agentilda::Launcher.new(ARGV.dup).execute!
107
30
  rescue SystemExit => e
108
- exit(help ? 0 : e.status)
31
+ exit(e.status)
109
32
  end
110
-
111
- exit 0
data/exe/tilda-rs ADDED
Binary file
@@ -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
@@ -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
- Agent = Data.define(:name, :description, :handles, :advances_to, :model,
29
- :allowed_tools, :may, :network, :timeout, :prompt, :path) do
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
- prefixed = all.select { |a| a.name.start_with?(wanted) }
100
- return prefixed unless prefixed.empty?
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
- # @param status [Agentilda::Status]
110
- # @return [Array<Agentilda::Agent>]
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