agentilda 1.0.3

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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +261 -0
  4. data/agentilda.gemspec +57 -0
  5. data/agents/hansolo-reviewer.md +29 -0
  6. data/agents/lando-broker.md +74 -0
  7. data/agents/leah-researcher.md +80 -0
  8. data/agents/luke-backend.md +81 -0
  9. data/agents/palpatine-planner.md +40 -0
  10. data/agents/rey-frontend.md +106 -0
  11. data/agents/yoda-writer.md +54 -0
  12. data/bin/create-plan-folder +125 -0
  13. data/bin/plan-number +164 -0
  14. data/exe/agentilda +111 -0
  15. data/exe/tilda +1 -0
  16. data/lib/agentilda/adoption.rb +192 -0
  17. data/lib/agentilda/agent.rb +136 -0
  18. data/lib/agentilda/brief.rb +234 -0
  19. data/lib/agentilda/cli/agents/subcommands/describe.rb +62 -0
  20. data/lib/agentilda/cli/agents/subcommands/list.rb +20 -0
  21. data/lib/agentilda/cli/base.rb +88 -0
  22. data/lib/agentilda/cli/create/create.rb +309 -0
  23. data/lib/agentilda/cli/docs/docs.rb +30 -0
  24. data/lib/agentilda/cli/index/index.rb +38 -0
  25. data/lib/agentilda/cli/linear/linear.rb +35 -0
  26. data/lib/agentilda/cli/linear/subcommands/import.rb +160 -0
  27. data/lib/agentilda/cli/linear/subcommands/projects.rb +55 -0
  28. data/lib/agentilda/cli/list_plans/list_plans.rb +21 -0
  29. data/lib/agentilda/cli/resync/subcommands/dirs.rb +49 -0
  30. data/lib/agentilda/cli/resync/subcommands/prs.rb +106 -0
  31. data/lib/agentilda/cli/run/run.rb +289 -0
  32. data/lib/agentilda/cli/states/states.rb +15 -0
  33. data/lib/agentilda/cli/unblock/unblock.rb +227 -0
  34. data/lib/agentilda/cli/version/version.rb +13 -0
  35. data/lib/agentilda/cli.rb +74 -0
  36. data/lib/agentilda/config.rb +44 -0
  37. data/lib/agentilda/control.rb +115 -0
  38. data/lib/agentilda/creator.rb +120 -0
  39. data/lib/agentilda/dev_work.rb +54 -0
  40. data/lib/agentilda/diagram.rb +144 -0
  41. data/lib/agentilda/documentation.rb +429 -0
  42. data/lib/agentilda/executor.rb +539 -0
  43. data/lib/agentilda/feature.rb +253 -0
  44. data/lib/agentilda/frontmatter.rb +36 -0
  45. data/lib/agentilda/github.rb +160 -0
  46. data/lib/agentilda/index.rb +206 -0
  47. data/lib/agentilda/keyboard.rb +88 -0
  48. data/lib/agentilda/linear/api.rb +220 -0
  49. data/lib/agentilda/linear/attribution.rb +185 -0
  50. data/lib/agentilda/linear/fuzzy.rb +68 -0
  51. data/lib/agentilda/linear/import.rb +298 -0
  52. data/lib/agentilda/linear/issue.rb +184 -0
  53. data/lib/agentilda/linear/mapping.rb +115 -0
  54. data/lib/agentilda/linear/push.rb +190 -0
  55. data/lib/agentilda/linear/survey.rb +173 -0
  56. data/lib/agentilda/linear/unit.rb +274 -0
  57. data/lib/agentilda/linear.rb +42 -0
  58. data/lib/agentilda/markdown.rb +56 -0
  59. data/lib/agentilda/ordinal.rb +90 -0
  60. data/lib/agentilda/progress_log.rb +122 -0
  61. data/lib/agentilda/publisher.rb +172 -0
  62. data/lib/agentilda/pull_request.rb +213 -0
  63. data/lib/agentilda/reporter.rb +175 -0
  64. data/lib/agentilda/resync.rb +358 -0
  65. data/lib/agentilda/roster.rb +110 -0
  66. data/lib/agentilda/runner.rb +456 -0
  67. data/lib/agentilda/state_machine.rb +355 -0
  68. data/lib/agentilda/status.rb +280 -0
  69. data/lib/agentilda/tally.rb +169 -0
  70. data/lib/agentilda/transcript.rb +435 -0
  71. data/lib/agentilda/tree.rb +77 -0
  72. data/lib/agentilda/ui.rb +681 -0
  73. data/lib/agentilda/unblocker.rb +207 -0
  74. data/lib/agentilda/version.rb +10 -0
  75. data/lib/agentilda/viewer.rb +60 -0
  76. data/lib/agentilda/worktree.rb +211 -0
  77. data/lib/agentilda.rb +155 -0
  78. data/lib/dry/cli/banner.rb +293 -0
  79. metadata +349 -0
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: palpatine-planner
3
+ description: Turns a signed-off specification into concurrently executable work units.
4
+ handles: [planned]
5
+ advances_to: building
6
+ model: opus
7
+ allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Skill]
8
+ writes: [plan.md, blocked.md]
9
+ ---
10
+
11
+ You are writing `plan.md` for one plan folder whose `spec.md` is complete.
12
+
13
+ Before drafting, invoke the `superpowers:writing-plans` skill (via the Skill tool) to structure the document. Use any other skill you find useful.
14
+
15
+ The spec says *what* and *why*. The plan says *in what order, by whom, and how we will know it worked*. Use `~/.agents/skills/create-plan/references/plan-template.md` as the shape.
16
+
17
+ ## The property that matters
18
+
19
+ Write it so several agents can execute it at once **without colliding**. That is not a slogan — it is a constraint you have to discharge explicitly:
20
+
21
+ - Every work unit names the files it **owns** and may write.
22
+ - Every work unit names what it **must not touch**.
23
+ - Two units that write the same file are not concurrent, whatever the diagram says. Say so, and sequence them.
24
+ - A unit that depends on nothing must say "depends on: nothing" out loud. That is the sentence that makes it startable now.
25
+
26
+ If the whole feature genuinely cannot be split, say that and say why. A false claim of concurrency is worse than an honest sequence.
27
+
28
+ ## Sizing
29
+
30
+ One work unit per pull request. If a unit cannot be described in a paragraph and verified by a reviewer in one sitting, split it. If the plan has more than about eight units, it is probably several plans — raise that rather than writing it.
31
+
32
+ ## When you cannot decompose without a decision
33
+
34
+ An ordering question that is not yours to settle, a dependency that turns on a product call, a unit whose scope depends on something nobody has ruled on: **do not guess**. Write `blocked.md`, each question as its own `## B1`, `## B2` heading, with options and a recommendation, and stop.
35
+
36
+ That notation is the whole of what the tool reads. A question written any other way leaves the folder looking unblocked, and the plan moves on as though you had never asked.
37
+
38
+ ## Done when
39
+
40
+ Each unit has an owner-set of files, a dependency statement, and a "done when" that a reviewer can check without reading your reasoning.
@@ -0,0 +1,106 @@
1
+ ---
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]
5
+ advances_to: ready_for_review
6
+ model: fable
7
+ allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Skill]
8
+ writes: ["**/*"]
9
+ ---
10
+
11
+ You are implementing **one** front-end work unit from `plan.md`. You have been given the plan folder and the unit to build.
12
+
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.
14
+
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.
16
+
17
+ ## Load the design skills before you write markup
18
+
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.
89
+
90
+ ## Done when
91
+
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.
93
+
94
+ ## When there is no front-end work left
95
+
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.
97
+
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.
99
+
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>`:
101
+
102
+ ```
103
+ git mv NNN.MM-🎨-<slug> NNN.MM-🟢-<slug>
104
+ ```
105
+
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.
@@ -0,0 +1,54 @@
1
+ ---
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.
4
+ handles: [researched, retroactive]
5
+ advances_to: planned
6
+ model: fable
7
+ allowed_tools: [Read, Grep, Glob, Bash, Write, Edit, Task, Skill, WebSearch, WebFetch]
8
+ writes: [spec.md, blocked.md]
9
+ ---
10
+
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:
12
+
13
+ - Introduction Section
14
+ - Deep Research Section produced by `leah-researcher`
15
+
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.
17
+
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.
19
+
20
+ ## What the final `spec.md` document should contain
21
+
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
40
+
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:
42
+
43
+ - an engineering or architecture decision → the folder becomes ⭕️
44
+ - a product or priority decision → the folder becomes 🅱️
45
+
46
+ A specification built on a guessed answer is worse than no specification, because it looks decided.
47
+
48
+ ## Retroactive plans
49
+
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.
51
+
52
+ ## Done when
53
+
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.
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # WARNING: deprecated in favor of "tilda create <words>" or
4
+ # "tilda create --from <file.md>"
5
+ #
6
+ # create-plan-folder — create the next numbered plan folder
7
+ #
8
+ # create-plan-folder [-D <dir>] <status> <topic words...>
9
+ #
10
+ # Prints the created path on stdout (so `cd "$(create-plan-folder ...)"` works);
11
+ # all chatter goes to stderr.
12
+ #
13
+ # Portable to bash 3.2 — the one Apple still ships in /bin/bash.
14
+
15
+ set -eo pipefail
16
+
17
+ usage() {
18
+ echo -e "
19
+ ${bldylw}DEPRECATED:${bldred}
20
+ This file is deprecated for use in creating directories. Partly because
21
+ it does not auto-generate the spec.md inside, while 'agentilda' executable
22
+ does.
23
+
24
+ Please use: ${txgrn}agentilda create [ word word ... ] | [ --from file.md ]
25
+
26
+ —————————————————————————————————————————————————————————————————————————————
27
+
28
+ ${bldcyn}USAGE:
29
+ ${txtylw}create-plan-folder [-D <dir>] <status> <topic words...>${clr}
30
+
31
+ ${bldcyn}WHERE:
32
+ -D <dir> Enclosing directory (default: ".")
33
+
34
+ ${txtgren}DESCRIPTION:${clr}
35
+ Status is a name or the emoji itself:
36
+
37
+ white | spec ⚪️ spec.md only, not yet planned
38
+ blue | planned | plan 🔵 spec.md + plan.md
39
+ yellow | open | wip 🟡 PR raised, not yet merged
40
+ green | done 🟢 all PRs merged
41
+ red | declined 🔴 decided never
42
+ hole | blocked ⭕️ needs a human decision (write blockers.md)
43
+ brown | later | defer 🟤 deliberately deferred (state the trigger)
44
+ purple | merged 🟣 PR-level status; see note below
45
+
46
+ ${bldcyn}EXAMPLES:${clr}
47
+ ${bldylw}create-plan-folder blue update sopsy with two new commands
48
+ create-plan-folder -D .plans white identity subcommand group${clr}
49
+ "
50
+ exit 64
51
+ }
52
+
53
+ [ $# -eq 0 ] && usage
54
+ case "${1:-}" in -h|--help|help) usage ;; esac
55
+
56
+ export dir="./.plans"
57
+ if [ "${1:-}" = "-D" ]; then
58
+ [ $# -ge 2 ] || usage
59
+ dir="$2"
60
+ shift 2
61
+ fi
62
+
63
+ [ $# -ge 2 ] || usage
64
+
65
+ # ---------------------------------------------------------------- status ----
66
+ # LC_ALL=C keeps tr byte-safe so multi-byte emoji pass through untouched.
67
+ case "$(printf '%s' "$1" | LC_ALL=C tr 'A-Z' 'a-z')" in
68
+ white|spec|'⚪️'|'⚪') emoji='⚪️' ;;
69
+ blue|planned|plan|'🔵') emoji='🔵' ;;
70
+ yellow|open|wip|'🟡') emoji='🟡' ;;
71
+ green|done|'🟢') emoji='🟢' ;;
72
+ red|declined|'🔴') emoji='🔴' ;;
73
+ hole|blocked|'⭕️'|'⭕') emoji='⭕️' ;;
74
+ brown|later|defer|deferred|'🟤') emoji='🟤' ;;
75
+ purple|merged|'🟣') emoji='🟣' ;;
76
+ *)
77
+ printf 'create-plan-folder: unknown status %s\n\n' "$1" >&2
78
+ usage
79
+ ;;
80
+ esac
81
+ shift
82
+
83
+ # ------------------------------------------------------------- next index ----
84
+ [ -d "$dir" ] || {
85
+ printf 'create-plan-folder: not a directory: %s\n' "$dir" >&2
86
+ exit 66
87
+ }
88
+
89
+ last=0
90
+ for path in "$dir"/[0-9][0-9][0-9]*; do
91
+ [ -d "$path" ] || continue # also skips the unexpanded glob
92
+ base=${path##*/}
93
+ digits=${base%%[!0-9]*} # leading run of digits, nothing else
94
+ [ -n "$digits" ] || continue
95
+ n=$((10#$digits)) # 10# so 008 isn't read as octal
96
+ if [ "$n" -gt "$last" ]; then last=$n; fi
97
+ done
98
+
99
+ next=$(printf '%03d' $((last + 1)))
100
+
101
+ # ------------------------------------------------------------------ slug ----
102
+ slug=$(
103
+ printf '%s' "$*" \
104
+ | LC_ALL=C tr 'A-Z' 'a-z' \
105
+ | LC_ALL=C tr -c 'a-z0-9' '-' \
106
+ | LC_ALL=C tr -s '-' \
107
+ | sed -e 's/^-*//' -e 's/-*$//'
108
+ )
109
+
110
+ [ -n "$slug" ] || {
111
+ printf 'create-plan-folder: topic produced an empty slug\n' >&2
112
+ exit 65
113
+ }
114
+
115
+ # --------------------------------------------------------------- create -----
116
+ target="$dir/${next}-${emoji}-${slug}"
117
+
118
+ if [ -e "$target" ]; then
119
+ printf 'create-plan-folder: already exists: %s\n' "$target" >&2
120
+ exit 73
121
+ fi
122
+
123
+ mkdir -p "$target"
124
+ # printf 'created %s\n' "$target" >&2
125
+ printf '%s\n' "$target"
data/bin/plan-number ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # plan-number - resolve the .plans folder a branch belongs to, or fail.
4
+ #
5
+ # plan-number # resolve for the current branch
6
+ # plan-number -b <branch> # ...for another branch
7
+ # plan-number -D <dir> # .plans lives elsewhere (default: ./.plans)
8
+ # plan-number --title "Foo" # print "[003] Foo" instead of "003"
9
+ # # (or "[018.01] Foo" for a retroactive plan)
10
+ # plan-number --none # print "XXX": this PR implements no plan
11
+ #
12
+ # Exit 0 and print the number on stdout when exactly one plan can be resolved.
13
+ # Exit non-zero and explain on stderr otherwise. It never picks a winner from
14
+ # several candidates and never invents one.
15
+ #
16
+ # == Why it refuses rather than guesses
17
+ #
18
+ # The number ends up in a PR title, and pull-requests.md is generated pfrom those
19
+ # titles. A wrong number does not announce itself: it files the work under a
20
+ # plan that did not do it, and the plan that did looks untouched. That is worse
21
+ # than an unprefixed title, which at least reads as "unknown" rather than as a
22
+ # confident lie. Every failure below is therefore loud.
23
+ #
24
+ # == The two shapes of number
25
+ #
26
+ # NNN an ordinary plan, specified before it was built.
27
+ # NNN.MM a RETROACTIVE plan: the work shipped first and the specification
28
+ # was written afterwards to describe what exists. It sits in the
29
+ # sequence where the work landed - 018.01 merged after 018 and
30
+ # before 019.
31
+ #
32
+ # NNN.MM does NOT mean "part of NNN". The dot reads as containment in most
33
+ # numbering schemes and here it does not: 018.01 is a sibling of 018 that
34
+ # arrived later, with no relationship to its subject. See .plans/README.md.
35
+ #
36
+ # The decimal is ALWAYS two digits. One digit would sort into the middle of the
37
+ # two-digit range - 018.09 < 018.1 < 018.10 - so a single mixed-width folder
38
+ # silently reorders the index. Two digits also give 99 slots per gap, which
39
+ # retires the question of running out: a gap closes the moment the next plan is
40
+ # created, since nothing merging today can land between 018 and 019.
41
+ #
42
+ # == The resolution order, strongest signal first
43
+ #
44
+ # 1. The BRANCH NAME. `kig/021-add-tune-appender` says 021 and says it before a
45
+ # single line is written, which is the only moment the author certainly knows
46
+ # the answer. Set once, carried on the PR record forever, survives squash.
47
+ #
48
+ # 2. The DIFF against the base, but ONLY when it touches exactly one plan
49
+ # folder. Measured across this repository's 24 merged pull requests, this
50
+ # alone resolves 6 and is ambiguous or silent on 18, so it is a fallback and
51
+ # never a primary. Files directly under .plans/ (BACKLOG.md, README.md,
52
+ # INDEX.md) are ignored: they belong to no single plan.
53
+ #
54
+ # 3. Nothing else. There is no "closest match by topic", because a heuristic
55
+ # that is right most of the time produces an index nobody can trust and
56
+ # nobody can audit.
57
+ #
58
+ # XXX is deliberately NOT step 4. A PR that implements no plan is a legitimate
59
+ # and common thing - dependency bumps, CI work, hotfixes - and it is marked
60
+ # [XXX] so that "no plan" is visibly asserted rather than merely absent. But the
61
+ # author asserts it; this script will not, because emitting XXX on a failed
62
+ # lookup would launder "I could not tell" into "there is definitely none", which
63
+ # is the same lie as guessing a number, told in the other direction.
64
+ #
65
+ # © 2026 Konstantin Gredeskoul
66
+
67
+ set -euo pipefail
68
+
69
+ PLANS_DIR=".plans"
70
+ BRANCH=""
71
+ TITLE=""
72
+ NONE=0
73
+ BASE="${PLAN_NUMBER_BASE:-origin/main}"
74
+
75
+ die() { printf '%s\n' "$*" >&2; exit 1; }
76
+
77
+ usage() {
78
+ cat >&2 <<'USAGE'
79
+ Usage: plan-number [-D <plans-dir>] [-b <branch>] [--base <ref>] [--title <text>] [--none]
80
+
81
+ Prints the plan number for the branch (NNN, or NNN.MM when the plan was written
82
+ retroactively), or fails loudly.
83
+
84
+ With --title, prints "[NNN] <text>" ready to hand to `gh pr create -t`.
85
+ With --none, prints XXX: an assertion that this PR implements no plan.
86
+ USAGE
87
+ exit 2
88
+ }
89
+
90
+ while [[ $# -gt 0 ]]; do
91
+ case "$1" in
92
+ -D|--dir) PLANS_DIR="${2:?}"; shift 2 ;;
93
+ -b|--branch) BRANCH="${2:?}"; shift 2 ;;
94
+ --base) BASE="${2:?}"; shift 2 ;;
95
+ -t|--title) TITLE="${2:?}"; shift 2 ;;
96
+ -n|--none) NONE=1; shift ;;
97
+ -h|--help) usage ;;
98
+ *) die "plan-number: unknown argument: $1" ;;
99
+ esac
100
+ done
101
+
102
+ # The three-digit number, optionally followed by one retroactive digit.
103
+ NUM_RE='[0-9]{3}(\.[0-9]{2})?'
104
+
105
+ if [[ "$NONE" == "1" ]]; then
106
+ if [[ -n "$TITLE" ]]; then printf '[XXX] %s\n' "$TITLE"; else printf 'XXX\n'; fi
107
+ exit 0
108
+ fi
109
+
110
+ [[ -d "$PLANS_DIR" ]] || die "plan-number: no $PLANS_DIR here. Run this from the repository root, or pass -D."
111
+
112
+ # Every plan number that actually exists on disk. A resolved number is checked
113
+ # against this, so a typo in a branch name fails here rather than in the index
114
+ # six weeks later.
115
+ existing="$(find "$PLANS_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; \
116
+ | grep -oE "^${NUM_RE}" | sort -u)"
117
+ [[ -n "$existing" ]] || die "plan-number: $PLANS_DIR holds no NNN[.MM]-<status>-<slug> folders."
118
+
119
+ exists() { printf '%s\n' "$existing" | grep -qx "$1"; }
120
+
121
+ emit() {
122
+ local n="$1"
123
+ exists "$n" || die "plan-number: resolved $n, but $PLANS_DIR has no folder $n-*.
124
+ Existing plans: $(printf '%s' "$existing" | tr '\n' ' ')"
125
+ if [[ -n "$TITLE" ]]; then printf '[%s] %s\n' "$n" "$TITLE"; else printf '%s\n' "$n"; fi
126
+ exit 0
127
+ }
128
+
129
+ [[ -n "$BRANCH" ]] || BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
130
+
131
+ # 1. The branch name. Matches 021 in kig/021-slug, 021-slug and feature/021_slug,
132
+ # and 018.01 in kig/018.01-slug. Longest match wins, so a retroactive branch
133
+ # does not silently resolve to its whole-number neighbour.
134
+ if [[ "$BRANCH" =~ (^|/|-|_)([0-9]{3}(\.[0-9]{2})?)($|[-_]) ]]; then
135
+ emit "${BASH_REMATCH[2]}"
136
+ fi
137
+
138
+ # 2. The diff, and only when it is unambiguous.
139
+ if git rev-parse --verify --quiet "$BASE" >/dev/null 2>&1; then
140
+ touched="$(git diff --name-only "$(git merge-base "$BASE" HEAD)"..HEAD -- "$PLANS_DIR" 2>/dev/null \
141
+ | awk -F/ 'NF>2 {print $2}' | grep -oE "^${NUM_RE}" | sort -u || true)"
142
+ count="$(printf '%s' "$touched" | grep -c . || true)"
143
+
144
+ if [[ "$count" == "1" ]]; then
145
+ emit "$touched"
146
+ elif [[ "${count:-0}" -gt 1 ]]; then
147
+ die "plan-number: this branch touches several plans and none is obviously primary:
148
+ $(printf '%s' "$touched" | tr '\n' ' ')
149
+ Name the branch after the one it implements (kig/NNN-slug), or pass the title yourself."
150
+ fi
151
+ fi
152
+
153
+ die "plan-number: cannot resolve a plan for branch '${BRANCH:-<detached>}'.
154
+
155
+ It carries no NNN in its name and its diff touches no single plan folder.
156
+
157
+ - If it implements a plan, rename the branch: git branch -m ${USER}/NNN-slug
158
+ - If it implements no plan, say so explicitly: plan-number --none --title '...'
159
+ gives you [XXX]. Infrastructure, dependency bumps and hotfixes are not
160
+ features, and [XXX] is the right answer for them - but it is your assertion
161
+ to make, not this script's guess.
162
+
163
+ Refusing rather than guessing: pull-requests.md is generated from these titles,
164
+ and a wrong number files the work under a plan that did not do it."
data/exe/agentilda ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # agentilda — spec → plan → build.
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.
15
+ #
16
+ # © 2026 Konstantin Gredeskoul
17
+
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
+
23
+ module Agentilda
24
+ PROJECT_ROOT = File.expand_path("..", __dir__)
25
+ end
26
+
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
70
+ end
71
+
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.
87
+
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
104
+
105
+ begin
106
+ Dry::CLI.new(Agentilda::CLI).call(arguments: help ? [] : argv) || 0
107
+ rescue SystemExit => e
108
+ exit(help ? 0 : e.status)
109
+ end
110
+
111
+ exit 0
data/exe/tilda ADDED
@@ -0,0 +1 @@
1
+ agentilda