@miroshnik/elves 0.0.0 → 2026.1018.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.
Files changed (68) hide show
  1. package/AGENTS.md +310 -0
  2. package/README.md +344 -0
  3. package/bin/ai-dev.ts +7 -0
  4. package/bin/elves.ts +2847 -0
  5. package/bin/shims/timeout +113 -0
  6. package/claude/CLAUDE.md +71 -0
  7. package/dist/github/scripts/github.js +3428 -0
  8. package/dist/spec/scripts/architecture.js +628 -0
  9. package/dist/spec/scripts/eslint-config.js +54 -0
  10. package/dist/spec/scripts/harness-qzb932c8.js +959 -0
  11. package/dist/spec/scripts/harness-v9s1v9w3.js +16045 -0
  12. package/dist/spec/scripts/harness.js +53 -0
  13. package/dist/spec/scripts/knip-hints.cjs +34 -0
  14. package/docs/ci-concurrency.md +82 -0
  15. package/docs/cloud-sessions.md +203 -0
  16. package/docs/judgment.md +81 -0
  17. package/docs/parallel-checkouts.md +220 -0
  18. package/docs/pr-checks.md +117 -0
  19. package/docs/testing.md +304 -0
  20. package/package.json +70 -1
  21. package/skills/ci-wait/SKILL.md +180 -0
  22. package/skills/ci-wait/scripts/wait-ci.sh +377 -0
  23. package/skills/dashboard/SKILL.md +212 -0
  24. package/skills/dashboard/scripts/dashboard.ts +912 -0
  25. package/skills/dashboard/scripts/sessions.ts +772 -0
  26. package/skills/est/SKILL.md +221 -0
  27. package/skills/est/reference.md +381 -0
  28. package/skills/est/scripts/est.ts +4854 -0
  29. package/skills/github/SKILL.md +206 -0
  30. package/skills/github/locales/en.json +449 -0
  31. package/skills/github/locales/ru.json +449 -0
  32. package/skills/github/reference.md +1073 -0
  33. package/skills/github/scripts/github.ts +4390 -0
  34. package/skills/github/scripts/locale.ts +495 -0
  35. package/skills/github/scripts/package.json +3 -0
  36. package/skills/slot/SKILL.md +145 -0
  37. package/skills/slot/scripts/package.json +3 -0
  38. package/skills/slot/scripts/slot.ts +279 -0
  39. package/skills/spec/SKILL.md +247 -0
  40. package/skills/spec/canon.md +219 -0
  41. package/skills/spec/checks.md +709 -0
  42. package/skills/spec/ci.md +378 -0
  43. package/skills/spec/migration.md +125 -0
  44. package/skills/spec/reference.md +889 -0
  45. package/skills/spec/scripts/architecture.ts +839 -0
  46. package/skills/spec/scripts/c4.ts +184 -0
  47. package/skills/spec/scripts/eslint-config.ts +82 -0
  48. package/skills/spec/scripts/harness.ts +1483 -0
  49. package/skills/spec/scripts/knip-hints.cjs +34 -0
  50. package/skills/spec/scripts/knip-hints.d.cts +17 -0
  51. package/skills/spec/scripts/package-run.ts +22 -0
  52. package/skills/spec/scripts/package.json +3 -0
  53. package/skills/spec/scripts/spec-break.ts +232 -0
  54. package/skills/spec/scripts/spec-claims.ts +198 -0
  55. package/skills/spec/scripts/spec-diff.ts +1111 -0
  56. package/skills/spec/scripts/spec-doc.ts +744 -0
  57. package/skills/spec/scripts/spec-exceptions.ts +233 -0
  58. package/skills/spec/scripts/spec-publish.ts +214 -0
  59. package/skills/spec/scripts/spec-run.ts +235 -0
  60. package/skills/spec/scripts/spec-shard.ts +267 -0
  61. package/skills/spec/scripts/speclib.ts +826 -0
  62. package/skills/spec/scripts/vendor/babel-parser.LICENSE +19 -0
  63. package/skills/spec/scripts/vendor/babel-parser.cjs +14615 -0
  64. package/skills/spec/scripts/vendor/babel-parser.d.cts +3 -0
  65. package/skills/task-env/SKILL.md +231 -0
  66. package/skills/task-env/proxy/compose.yaml +51 -0
  67. package/skills/task-env/reference.md +254 -0
  68. package/skills/task-env/scripts/task-env.ts +1017 -0
@@ -0,0 +1,1073 @@
1
+ # github — canon reference: GitHub project and tasks
2
+
3
+ Principles — `AGENTS.md`, section "Task tracking". Here are the rules in force
4
+ in full: how tasks are organized (types, decision labels, epics, dependencies),
5
+ the GitHub project, milestones and the five epic-refresh checks on task close;
6
+ then the details of the skill's commands — what `project check` verifies and
7
+ `project fix` does, what each `task` and `pr` command checks, sets and prints —
8
+ and the full git, PR and merge rules; at the end — manual commands for what
9
+ the skill's commands don't cover (editing an already created task and
10
+ milestones). When to call each command — `SKILL.md`; syntax —
11
+ `github <command> --help`. Examples use made-up owners and numbers.
12
+
13
+ ## Task tracking — structure
14
+
15
+ - Tasks live in the project repository's **GitHub Issues** (the `github`
16
+ skill: `task new`, `task status`, `task drop`), not in local lists,
17
+ TODO files or the agent's built-in task list.
18
+ - **Every issue has a type** (the organization's Issue type, not a label) —
19
+ exactly three: **Task** (the default: a unit of work for one PR), **Bug**
20
+ (a defect: something that already exists works wrong), **Epic** (a big task
21
+ with subtasks). There are no other types: the default Task/Bug are
22
+ named in the project language (`issueTypes` in `locales/<language>.json`
23
+ of the `github` skill), Feature is disabled. The type is set on creation
24
+ (`github task new --type`); `github project fix` configures the
25
+ organization's types. Repositories of a personal account have no types —
26
+ there an epic gets the `epic` label; this is the only exception.
27
+ - **Decision labels** — the decision's name (folder `tests/…/<name>` or a
28
+ model module) without a kind prefix; the kind is shown by color: capability
29
+ green, standard blue, architecture orange; one name across different kinds
30
+ is one label. A code task carries the label of every decision it introduces
31
+ or changes (`github task new --labels`, from the PR diff —
32
+ `github pr labels <PR>`; a mechanical edit — a deletion, a rename without
33
+ changes, the decision folder's exceptions file that spec reads as an
34
+ exception: `exceptions/*.json`, the former `exceptions.*` — doesn't change
35
+ the decision and gives no label); no labels — only a task without code. An epic has the
36
+ union of its subtasks' labels. `github project check` checks the set
37
+ against the spec tree of the main branch.
38
+ - **Project slice labels** — the team's own slice of tasks (apps `web`,
39
+ `api`, `admin`; teams), not decisions: the marker is the description
40
+ `Slice: <slice>[ — note]` (a decision label has `Decision: …`). The name
41
+ may coincide with a model module: then
42
+ `project check` prints a `○` line with the slice, `fix` doesn't touch the
43
+ label, and the decision stays without a label (the name is taken);
44
+ `pr labels` doesn't set such a label as a decision label, `task new
45
+ --labels api` sets it as a regular label. A slice's color is not a decision
46
+ kind's color: a decision's kind is visible only by color.
47
+ - **A small task** — one issue of type Task or Bug.
48
+ - **A big task** — an epic + subtasks:
49
+ - the epic is an issue of type Epic: goal, context, the agreed design,
50
+ questions, definition of done; **no list of subtasks, their order or
51
+ stages** — the plan is the sub-issues themselves, the order — *blocked
52
+ by*, the stages — milestones (the epic's `Sub-issues` and
53
+ `Relationships` show them); a copy in text goes stale with the first
54
+ change of a link;
55
+ - the epic has a **short prefix** (`Billing`, `Import`, `API`): the titles
56
+ of the epic and all its subtasks start with it (`<Prefix> · <title>`),
57
+ and it also goes into milestone names;
58
+ - each subtask is a separate issue attached to the epic as a **sub-issue**
59
+ (native GitHub sub-issues; `github task new --epic N`);
60
+ - a subtask must be a complete unit of work that can be done in one PR;
61
+ - an epic may split its plan into stages — then a stage is a milestone
62
+ (section "Milestones — sets of tasks" below) and each subtask belongs to
63
+ one stage; an epic without stages has no milestones.
64
+ - **Dependencies — only GitHub's built-in links** (Relationships), not text
65
+ in the description: the parent — sub-issue (the epic), "is blocked by" —
66
+ *blocked by*, "blocks" — *blocking*; on creation —
67
+ `github task new --blocked-by`.
68
+ We don't write "Depends on" / "After #N" sections. **An external
69
+ blocker** that is an issue of another repository (an upstream bug) — also
70
+ *blocked by*: GitHub links issues of any owner (`gh issue edit <N>
71
+ --add-blocked-by <issue URL>`). A blocker that isn't an issue (a release, a
72
+ PR, another tracker, a client's answer, an external deadline) — the label
73
+ `external blocker` in the project language (`labels.externalBlocker`; not in
74
+ the repository yet — `gh label create`) and a comment that starts with
75
+ `External blocker:` (`comments.externalBlocker`) and gives the reason; the
76
+ blocker is lifted — the label is removed. *Relates to* — only in the UI and
77
+ only if the link isn't "blocks". A task with an open *blocked by* or that
78
+ label isn't taken into work: `task queue` shows it as `○` with the blocker.
79
+ - Before creating a new task we check that it doesn't exist yet
80
+ (`gh issue list --search`); before starting work — that it isn't already
81
+ going in a parallel session (`AGENTS.md`, section "Git, PRs and merging").
82
+ - **Nothing native is duplicated in text:** status — only the project's
83
+ `Status` field (section "Project (GitHub Projects)" below), links — only
84
+ sub-issue and blocked by, a stage — only the milestone, the decisions a
85
+ task changes — only decision labels, estimate and actual — only the fields,
86
+ the PR — only `Closes #N`; the issue's text holds what GitHub doesn't keep
87
+ (goal, context, the agreed design, questions, scenarios). An epic is closed
88
+ when all its subtasks are closed.
89
+ - No GitHub repository — say so and ask where to keep tasks, rather than
90
+ silently filing them somewhere else.
91
+
92
+ ## Project (GitHub Projects)
93
+
94
+ Every repository has **exactly one** GitHub Project linked to the repo
95
+ (`github project fix`); the project name = the repository name. It holds
96
+ **all** epics and tasks of the repository — we don't create separate projects
97
+ per epic/area (an area = an epic inside the shared project). All tasks are
98
+ added to the project right on creation (`github task new`). Required:
99
+
100
+ - **Three views**, with exactly these names: `Board` (BOARD_LAYOUT, columns
101
+ by `Status`), `Table` (TABLE_LAYOUT), `Roadmap` (ROADMAP_LAYOUT).
102
+ - **The `Status` field** with options `Backlog` → `In progress` → `Done`.
103
+ I change the status **myself, immediately**, without waiting to be asked
104
+ (`github task status <N> <status>`):
105
+ - a new task — I set Backlog on creation;
106
+ - took a task into work — I set In progress before starting the work;
107
+ - finished (PR merged / task closed) — `github task close <N>` sets Done
108
+ together with the actual, the epic, the milestone and removing the merged
109
+ branches;
110
+ - the task was reopened (the bug came back, the PR was reverted) — I set
111
+ In progress again (or Backlog if no one is working on it); an already
112
+ closed milestone and epic I reopen as well;
113
+ - **closed without being done** (won't do, duplicate, replaced by other
114
+ tasks) — I close it as not planned and **remove it from the project**
115
+ (`github task drop <N>`): it doesn't get into Done and doesn't count in
116
+ the epic's estimate.
117
+ Epic: In progress when its first subtask is taken; Done when all are done
118
+ (those closed as not planned don't count — they are no longer in the
119
+ project).
120
+ Safety net — the project's built-in workflows "Item added to project" →
121
+ Backlog, "Item closed" → Done and "Auto-add to project" (new open issues of
122
+ the repository get into the project by themselves): the status gets set
123
+ even if there is no one to set it (a cloud session, another agent, closing
124
+ from the GitHub UI). The agent sets the status anyway; a task closed as not
125
+ planned it removes from the project — otherwise the workflow puts it into
126
+ Done.
127
+ - **The `Estimate, h` field** (Number) — the agent's active hours
128
+ (`AGENTS.md`, section "Estimate and actual — the `est` skill"). Set **only
129
+ by `github task new`** (the `est` script inside) — from analogs with
130
+ actuals picked by the script, not off the top of my head: for every subtask
131
+ and small task on creation; fewer than two analogs in the repository — no
132
+ estimate; for an epic — the sum of its subtasks' estimates, recalculated
133
+ when a subtask is added or its estimate changes. After work starts I don't rewrite the estimate — the
134
+ discrepancy is explained in the actual comment.
135
+ - **The `Actual, h`, `Tokens, M`, `Cost, $` fields** (Number) — the actual:
136
+ active hours, all tokens
137
+ of the linked model responses (input, output, cache write and read,
138
+ including subagents) and their price at public API rates — an **API
139
+ equivalent**: not charged on a subscription, but comparable across tasks
140
+ and models. Filled only by `est fact --write` when the task is closed,
141
+ never touched by hand; for an epic — the sum of its subtasks.
142
+ - **Priority — the issue's built-in `Priority` field** (an organization Issue
143
+ field it has by default: `Urgent` → `High` → `Medium` → `Low`), **not** a
144
+ project field of our own.
145
+ Required on every open task: a new one gets `Medium` unless the user said
146
+ otherwise; an epic — no lower than its most urgent open subtask. The field
147
+ is added to the project, `Table` and `Board` are **sorted by it**; within
148
+ a priority the order is manual. We take into work the most urgent of the
149
+ unblocked. A personal account has no issue fields — no priority is kept.
150
+ `github task new` sets it (`--priority`, `Medium` by default).
151
+
152
+ Nothing extra: there are no other fields (Iteration, Readiness…) or views
153
+ until they are added here. Readiness — the status and *blocked by*; stages —
154
+ milestones (the `Milestone` field).
155
+
156
+ The project — the `github` skill: `project check` once per session before
157
+ working with tasks, ❌ — `project fix`. No project — `fix` links one, copies
158
+ the template or creates one; what the API lacks (workflows, sorting) — UI
159
+ steps: the agent goes through them with the session's browser without asking
160
+ separately, without a browser — links to the user. Deleting or renaming in a
161
+ project that has already been used and changing organization settings — only
162
+ after confirmation (`fix --confirm`). What exactly `check` verifies, item by
163
+ item — section "What is checked" below.
164
+
165
+ ## Milestones — sets of tasks
166
+
167
+ A milestone (a built-in repository entity) is **just a named set of tasks**
168
+ with a common goal and, if there is one, a due date; we use nothing else for
169
+ grouping (sprints, our own "Stage"/"Phase"/"Release" field). Meaning decides
170
+ the contents: plain tasks (`Release 2026-10`), whole epics (the milestone is
171
+ on the epics themselves), **an epic's stage** (a set of its subtasks).
172
+
173
+ - **Optional** for both a task and an epic: created only when a group has to
174
+ be tracked as a whole. An epic without a stage plan has no milestones.
175
+ - **Name** of a stage: `<Epic> · <N> · <Stage name>`
176
+ (`Billing · 1 · Data model`), `N` — the stage number in the plan (from 0 or
177
+ from 1); a set without an epic — a meaningful name without a prefix; names
178
+ must not be confusable. **Description** of a stage: `Epic: #<number>`; of
179
+ another set — the goal in one or two lines.
180
+ - **An issue has only one milestone**, so we don't mix levels: subtasks go
181
+ into their epic's stages; into a cross-cutting set (a release, a demo) we
182
+ put the epic itself or plain tasks.
183
+ - **I set it on creation** of a task if the task belongs to a set; stage
184
+ milestones I create together with the epic, as soon as the plan is agreed
185
+ (later stages may be refined).
186
+ - **A stage not yet broken down** → the milestone has one stage task with a
187
+ checklist (`<Epic> · Stage N. …`, a sub-issue of the epic, an estimate for
188
+ the whole stage; an exception to the "one PR" rule; its items aren't issues
189
+ yet, so the checklist isn't a copy). Decomposition: issues
190
+ from the checklist items (the same milestone, sub-issues, with estimates),
191
+ then the stage task — not planned and out of the project.
192
+ - **Due date** — only a real deadline (then the set shows on the roadmap).
193
+ - **I close it myself** when the set's last task is closed; I reopen it if a
194
+ task is reopened. On task creation — `github task new --milestone`, the
195
+ other commands — section "Manual commands" below.
196
+
197
+ ## Epic refresh on task close
198
+
199
+ The task's PR is merged — right after the merge **a subagent with a fresh
200
+ context** goes through all open tasks of the same epic and brings them in line
201
+ with what shipped; the session at peak context doesn't do it itself — each of
202
+ its turns costs several times more. This is part of closing the task, not a
203
+ separate request. The refresh doesn't depend on the deploy: the subagent and
204
+ the deploy wait (`ci-wait`) run in the background in parallel,
205
+ `github task close` — after both. What needs no judgment `github task
206
+ actualize <N>` does itself: blocked by on done tasks is removed (check 4), the
207
+ `questions` label without open questions in the section (check 2), a milestone
208
+ of the epic's tasks without open tasks is closed (check 1; the task's and the
209
+ epic's own — by `task close`); its fixes — one line in a comment on the epic.
210
+ Then it prints the subagent's two-line prompt; the whole assignment — the
211
+ texts of the epic and its open tasks, the PR files, the copies of links in
212
+ text and the answers to open questions in comments the script found (named,
213
+ not edited: a line may carry meaning, a comment's numbering may be its own),
214
+ the checks by meaning 1, 3 and 5 — is printed by `task actualize <N>
215
+ --assignment`, the subagent's first call. No epic or no open tasks in it — the
216
+ line `○ refresh not needed`, no subagent. Five checks:
217
+
218
+ 1. **Done is closed.** No open tasks that are already part of what shipped
219
+ (done — I close it; lost its point — not planned and out of the project).
220
+ Statuses and milestones match: the set's last task is closed — I close the
221
+ milestone.
222
+ 2. **Answered questions are marked.** Every already answered question in
223
+ `## Questions` (comments, another task, code) is marked ✅ with the answer
224
+ (`github task answer <N> <k> "<answer>"`); the numbering doesn't change —
225
+ it is referenced. None open — the `questions` label goes (`task answer`
226
+ and `task actualize` remove it).
227
+ 3. **Descriptions don't lie.** The wording of open tasks is checked against
228
+ the code and specs: what doesn't exist after the rollout (fields, tables,
229
+ routes, permissions) is removed. After **a reversal of an accepted
230
+ decision** I edit every task where it is quoted.
231
+ 4. **Dependencies are real and only links.** GitHub links (sub-issue,
232
+ blocked by) reflect the actual state: blocked by on closed tasks is
233
+ removed, a new task is attached to the epic. Their copy in text — a list
234
+ of subtasks in the epic, an order, "⛔" or "after #N" marks — is removed.
235
+ 5. **Repetition is a standard.** A decision that, in what shipped, repeats an
236
+ existing one in two or more places — a `Standard · …` issue
237
+ (a rule in `tests/standards/` and a refactoring). First a search among the
238
+ open `Standard · …`: one exists — don't file another; an extension of a
239
+ neighboring one — a comment in it. A standard's priority — by the cost of
240
+ the mistake it prevents; standards of one area — as an epic: otherwise
241
+ they are born faster than they are worked off. Something deferred as "not
242
+ in this PR" — an issue or dropped explicitly, not forgotten.
243
+
244
+ What was fixed — one comment on the epic, the history of decisions in one
245
+ place; it is the subagent's last step, always: nothing to fix — «the block is
246
+ current». The comment starts with «Refresh after #N, result:» in the project
247
+ language (`report` of the dictionary) — it is the end of the refresh for
248
+ `task close`: until it, close refuses (#571: an executor closed its task while
249
+ its refresh still ran in its worktree, the dispatcher's queue removed the
250
+ worktree, and the subagent was left without a directory).
251
+
252
+ **End of session — one line, one of three.** A missing line can't be told
253
+ from a forgotten one, so the answer always has the last line:
254
+
255
+ - **`All done. The session can be closed.`** — everything is done: the PR is
256
+ merged; the post-merge deploy was
257
+ awaited (where the merge deploys); `task close` passed without `!` lines;
258
+ the epic refresh is done; there are no open questions to the user or in
259
+ issues; proposed tasks are filed in Backlog.
260
+ - **`All done, but there are questions: …`** — the same is done, but the
261
+ owner has something to decide beyond the
262
+ task: whether to file a task for an observation ("your call"), whether to
263
+ revert a setting, whether to cut a release. The questions themselves — in
264
+ this same line, briefly, one per decision; `The session can be closed` is
265
+ not written: with it the questions read as
266
+ "nothing to decide".
267
+ - **`Remaining: …`** — not everything is done: what the session
268
+ waits for and from whom (a merge from the owner, an answer to the task's
269
+ question, a red check).
270
+
271
+ Information without a decision — a period to wait out, a warning ("didn't
272
+ make it into the release"), an observation with nothing to decide — is not a
273
+ question: it goes into the report above, and the line is
274
+ `All done. The session can be closed.`. A report of what was done (what was
275
+ merged, links to PRs and tasks) is not a question either. A session without a
276
+ task (epic planning, an investigation) ends the same way — by what it was
277
+ assigned.
278
+
279
+ **In auto mode the session archives itself at the end.** A project with
280
+ `"auto": true` in `.agents/elves.json` runs the task entirely without asking,
281
+ and closing the session is the last step left to a human: after the line
282
+ `All done. The session can be closed.` the session archives itself — as its
283
+ last action, after which the conversation ends. After the other two lines and
284
+ outside auto mode — no: an archived session leaves the list, and the owner
285
+ will no longer read the questions in its last answer. Before archiving the
286
+ session stops its own background tasks still running (a wait, a watcher over
287
+ its output, a subagent): by this line everything needed has finished, and a
288
+ live leftover blocks archiving (#437). How to archive — in the
289
+ agent's file (`claude/CLAUDE.md`); an agent without such a tool (Codex, a
290
+ cloud session) keeps the line. Archiving isn't deletion: the session can be
291
+ restored.
292
+
293
+ ## Project check and fix
294
+
295
+ When to run `project check` and `project fix`, their codes — `SKILL.md`. Here —
296
+ what is checked item by item, who does what in `fix`, how to go through the UI
297
+ steps and the API's gaps the script exists for. Done by hand, projects drift
298
+ apart: somewhere "Item closed" is off, somewhere a filter hides tasks from the
299
+ board.
300
+
301
+ ### What is checked
302
+
303
+ Names of views, `Status` options, fields, issue types and label markers here
304
+ and below are the `en` ones (in the project language — `locales/<language>.json`
305
+ of the `github` skill). The project language — `language` in
306
+ `.agents/elves.json` of the checkout the script runs from (until `update` moves
307
+ it — the former `.agents/ai-dev.json`; module `scripts/locale.ts`). Views,
308
+ `Status` options, fields and issue types named in another language of the
309
+ dictionary — ❌, `fix` renames them keeping the ids
310
+ (task values and the «Item closed» target survive; with tasks in the project —
311
+ after `--confirm`); a decision label with a marker in another language — its
312
+ description is rewritten.
313
+
314
+ 1. The project is linked to the repository, it is the only one, its title =
315
+ the repository name.
316
+ 2. Views `Board` (BOARD, columns by `Status`), `Table` (TABLE), `Roadmap`
317
+ (ROADMAP), filters empty, no others.
318
+ 3. `Status`: `Backlog` → `In progress` → `Done`.
319
+ 4. Fields «Estimate, h», «Actual, h», «Tokens, M», «Cost, $» (Number), no
320
+ custom fields beyond the canon.
321
+ 5. Organization only: `Priority` — the organization's issue field, connected,
322
+ `Table` and `Board` are sorted by it; issue types — exactly «Task»,
323
+ «Bug», «Epic». A personal account has none — a `➖` line.
324
+ 6. Workflows "Item added to project" (→ Backlog), "Item closed" (→ Done),
325
+ "Auto-add to project" (`is:issue is:open`) are on.
326
+ 7. Closed tasks — in Done; those closed without being done (not planned,
327
+ duplicate) — outside the project.
328
+ 8. The repository's open tasks — in the project, each has a `Status`.
329
+ 9. Decision labels — by the spec tree of the default branch (via the API): the
330
+ label name is the decision name (folder
331
+ `tests/capabilities|standards|architecture/<name>` or a model module), kind
332
+ — by color: capability `0E8A16`, standard `1D76DB`, architecture `D93F0B`.
333
+ One name for decisions of different kinds — one label: the color of the
334
+ senior kind (capability → standard → architecture), all paths in the
335
+ description. A decision label is recognized by the description
336
+ `Decision: …`. A decision without a label — the label is created. A label
337
+ without a decision on an open task — a new decision (`task new` puts it, it
338
+ reaches the default branch with the task's PR): an `○` line with the task
339
+ numbers, item ✅, the label is left alone. A label without a decision and
340
+ without open tasks — deleted with `--confirm`; one such and one missing of
341
+ the same kind — a rename with `--confirm` (tasks keep the label); a foreign
342
+ color or description — fixed. An old `kind:name` label is renamed to `name`
343
+ by itself, whether or not the decision is in the default branch; the name is
344
+ already taken — a merge with `--confirm` (tasks get the label with the name,
345
+ the old one is deleted). A regular label with a decision's name — ❌, it
346
+ becomes a decision label with `--confirm`. A slice label — another slice of
347
+ the project's tasks (apps, teams), recognized by the description
348
+ `Slice: <slice>[ — note]`: with a decision's name — an `○` line
349
+ with the slice, item ✅, `fix` doesn't touch it, the decision stays without a
350
+ label. The model (`tests/architecture/model.ts`) is read by a separate
351
+ process — if it didn't load, module labels are left alone, ❌ with the
352
+ reason.
353
+ 10. Merging into the default branch — only with green required checks: an
354
+ active branch rule (`Ref.rules` — a repository or organization ruleset —
355
+ or classic protection) with a non-empty list of required checks. None —
356
+ `fix` sets the `elves` ruleset: `~DEFAULT_BRANCH`, without strict (the
357
+ rule doesn't require an up-to-date branch), required — the checks green on
358
+ each of the last 10 merged PRs (by the latest run; skipped or neutral
359
+ isn't green here), bypass — the admin role (the owner's direct
360
+ push). Its own ruleset keeps being checked: disabled, with strict or
361
+ without bypass — fixed; under the flow's former name `ai-dev` (set by a
362
+ `fix` before the rename to Elves) — its own too, `fix` renames it to
363
+ `elves` with the same edit; a required check that the fresh head lacks (the
364
+ last merged PR, an open PR with finished checks) leaves the list —
365
+ otherwise a PR would wait for it forever; a new stable one is added.
366
+ Someone else's rule with required checks — ✅, with strict or without: no
367
+ own ruleset is created, the other rule isn't edited. A private repository
368
+ on Free (GraphQL returns empty rules, REST — 403 "Upgrade to GitHub Pro")
369
+ and merged PRs without a common green check — a `➖` line. This item
370
+ doesn't need the project.
371
+ 11. PR merge method — rebase only (canon, "PR"): in the repository settings
372
+ merge commit and squash are off, rebase is on — otherwise a PR would be
373
+ merged past the merge rule, and `gh pr merge --rebase` would be refused.
374
+ `fix` sets this with one REST request (`PATCH repos/{owner}/{repo}`:
375
+ GraphQL doesn't change merge settings); it needs admin rights — without
376
+ them a `!` line with GitHub's response and a link to the settings. This
377
+ item doesn't need the project.
378
+
379
+ ### fix: who does what
380
+
381
+ - **API — the script itself:** linking, a template copy or a new project,
382
+ missing views and fields, view layout, removing a filter, `Status` options,
383
+ the `Priority` issue field, task statuses, adding open tasks, removing tasks
384
+ closed without being done from the project, decision labels (creation,
385
+ color), the default-branch ruleset (creation, editing its own), merge
386
+ methods — rebase only (REST). `+` lines.
387
+ - **UI steps** (`UI steps …`) — what the API lacks. Calling
388
+ `fix` is permission to configure this repository's project: the agent goes
389
+ through the steps itself, in a browser where the user is signed in to GitHub
390
+ (Claude in Chrome or the built-in browser after they sign in), without asking
391
+ separately. No browser or sign-in — a list of steps with links for the user.
392
+ The agent doesn't enter a password. After the steps — `check` again.
393
+ - **Confirmation** (`Confirmation needed …`) —
394
+ deleting and renaming in a project that already has tasks, unlinking extra
395
+ projects, the organization's issue types (shared by all its repositories),
396
+ deleting and renaming decision labels (they are on tasks). Show the list to
397
+ the user, after a "yes" — `fix --confirm`. In a new project without tasks,
398
+ deleting and renaming go without confirmation.
399
+ - **Error** — a `!` line with the reason (and a link to the UI if the step is
400
+ done there): most often `gh` lacks a scope.
401
+
402
+ ### UI steps — how to go through them
403
+
404
+ - **Workflow:** the link leads to a configured workflow
405
+ (`<project>/workflows/<fullDatabaseId>`) or to the list `<project>/workflows`,
406
+ where an unconfigured one is picked in the left menu. Edit → configure →
407
+ **Save and turn on workflow**; `On` appears at the top right. In Set value
408
+ `A value is required` — the `Status` option the workflow pointed at was
409
+ deleted (options were replaced without ids): pick it again.
410
+ - "Item added to project": trigger `issue, pull request` (default), Set
411
+ value → `Status: Backlog`.
412
+ - "Item closed": Set value → `Status: Done`.
413
+ - "Auto-add to project": the repository — this project's, filter
414
+ `is:issue is:open`, Enter — the "See N existing items" counter should
415
+ update. Without `is:open` an updated closed task (a not planned one too)
416
+ comes back into the project. Auto-add doesn't add already existing tasks —
417
+ `fix` adds them.
418
+ - **Sorting by Priority, board columns:** the view → the `View` button on the
419
+ right → `Sort by: Priority` / `Column by: Status` → `Save view` → the dialog
420
+ "Save display options for …?" → `Save`. Escape closes the dialog without
421
+ saving: the sorting stays only in the URL (`?sortedBy…`), and the tab's
422
+ unsaved marker lies — `check` verifies the result.
423
+ - **The organization's issue fields** (the organization has no `Priority`) —
424
+ the organization settings via the link from the step, then `fix` again.
425
+
426
+ ### Details the script exists for
427
+
428
+ - **A workflow can't be turned on via the API, and its target can't be read:**
429
+ `ProjectV2Workflow` returns only `name`, `number`, `fullDatabaseId`,
430
+ `enabled`; there is no mutation to turn it on. An unconfigured workflow isn't
431
+ in the list at all (for a new project — "Item closed", "Pull request merged",
432
+ "Auto-close issue").
433
+ - **The "Item closed" target is seen from the result:** a closed task not in
434
+ Done — the workflow is off or points at a deleted `Status` option. `fix` sets
435
+ Done and leaves a step to check the target in the UI.
436
+ - **`Status` options are renamed keeping the same id**
437
+ (`updateProjectV2Field`, `singleSelectOptions[].id`): task values and the
438
+ "Item closed" target are kept. Replacing options without ids gives new ids —
439
+ task statuses vanish, "Item closed" sets the deleted `Done`.
440
+ - **The template** — the elves project (`miroshnik/6`): everything the API
441
+ lacks is configured there once. A copy (`copyProjectV2`) carries over views
442
+ with their settings, fields and configured workflows, except auto-add; it
443
+ doesn't carry over tasks, collaborators or the repository link. The template
444
+ is unavailable (someone else's account) or `--template none` — the project is
445
+ created from scratch (`createProjectV2`) and brought up via the API:
446
+ `Todo / In Progress / Done` → the canon with the same ids, `View 1` →
447
+ `Table`, the rest is created.
448
+ - **The view filter is empty:** `iteration:@current` without an Iteration field
449
+ silently hides tasks from the board.
450
+ - **`Priority` is the organization's issue field** (`isIssueField`), not a
451
+ project field of the same name. The API reads the sorting (`sortByFields`)
452
+ but doesn't set it; the board grouping too (`verticalGroupByFields`).
453
+ - **A project that has been used** — it has tasks.
454
+
455
+ ## Task and PR commands — details
456
+
457
+ When to call each command — `SKILL.md`; syntax — `github <command> --help`.
458
+ Here — what each one checks, sets and prints.
459
+
460
+ ### task new
461
+
462
+ Without `--body` the description is a skeleton in the project
463
+ language: the scenarios and questions sections (an epic — only questions);
464
+ `--type` and `task status` take a name in any language of the dictionary. A
465
+ board in another language of the dictionary (the script runs outside the
466
+ project's checkout, the language changed without `fix`) — the task is kept in
467
+ the board's language. One `createIssue` mutation: the organization's issue type (in
468
+ a personal account an Epic gets the `epic` label, created if missing; Task and
469
+ Bug get no label there), the project, the epic (sub-issue), the milestone by
470
+ title among open ones, `Priority` in an organization (`Medium` by default).
471
+ Then `Status: Backlog` — by itself, without waiting for the workflow — and
472
+ blocked by. A subtask's title gets the epic's prefix (`<Prefix> · …`) if it
473
+ lacks it; the epic's `Priority` is raised to the subtask's if the subtask is
474
+ more urgent. Everything is checked before creating — an error (code 2) doesn't
475
+ leave a half-made task: `--epic` is an open epic, the milestone exists, the
476
+ blockers exist (closed ones are skipped), there is no open task with the same
477
+ title, the type is enabled, `Priority` is one of the options; in a personal
478
+ account `--priority` is an error. `--labels` — the task's labels in the same
479
+ mutation: a decision from the default branch — by name (`billing`), a new
480
+ decision — `kind:name` (`capability:export`, the label is `export`, the line
481
+ «new decision»: it will appear in the task's PR); no decision
482
+ label — created with the kind's color; a regular label must exist, otherwise an
483
+ error before creating. With `--epic` the decision labels are added to the epic
484
+ too (the union of its subtasks; GitHub's limit of 100 — "pr labels"). A slice label by name (`api`) is a regular task label,
485
+ not passed to the epic; `kind:name` with a slice label's name is an error
486
+ before creating: a decision with that name isn't marked by a label. A question
487
+ without ✅ in the description's questions section — the `questions` label in
488
+ the same mutation (created if missing), a `+` line names the open questions;
489
+ none open — no label. Prints `Created #N …`, `+` lines and the next step.
490
+
491
+ ### The estimate — by task new itself
492
+
493
+ Like the actual in `task close`:
494
+ after creating a task or a bug it runs `est estimate <N> --type <branch type>
495
+ --write` by the est script next to it (`../est/scripts/est.ts`) and prints its
496
+ output — the script picks the analogs, the agent has no step and doesn't open the
497
+ `est` skill. `--branch-type` — the type of the task's branch (`feat fix docs
498
+ refactor perf test chore ci build research`) and of its estimate: required for
499
+ a task, a bug without it is `fix`, an epic gets none (error before creating —
500
+ an epic has no branch and no estimate of its own). Fewer than two analogs with
501
+ an actual in the repository — est prints «No estimate», the task stays without
502
+ one; est is missing — the line `○ no estimate`, failed — its error as a `!`
503
+ line; the task is created anyway (code 0). In a session with a
504
+ pinned task (`task status` below) — a second line «Next:»: the session runs
505
+ `owner/repo#N`, `#M` isn't taken here — the question to the owner «queue #M?»,
506
+ "yes" — a background subagent in its own worktree with the printed prompt (the
507
+ executor's, as in `task queue`): the session launches it at once and goes on
508
+ with its own task (#546; a session card waited for the owner's click). An
509
+ epic — instead, after the line about its estimate: once its subtasks are created,
510
+ the question «run the dispatcher on epic #M?», "yes" — `task queue
511
+ --epic M` in this session.
512
+
513
+ ### task show
514
+
515
+ The task in one output instead of `gh issue view` per piece
516
+ (the description, then the epic, its subtasks, the PR and its checks): the
517
+ title and link; the type, the state if closed, `Status`, the estimate and the
518
+ actual, `Priority`, milestone and labels in one line; the epic with its open
519
+ questions and other open tasks (`- question k. …`, `- task #M «…»`); an
520
+ epic's open subtasks; open blockers; each PR with its state and its head's
521
+ checks (`checks: 2 ✅, 1 ⏳ (e2e), 1 ❌ (lint)`; skipped and neutral — ✅,
522
+ cancelled — ❌, of a check's reruns — the latest, as `ci-wait` reads it:
523
+ standard `check-run-state`; GitHub hasn't linked the PR yet
524
+ — found by the branch or «Closes #N», as `task close` does; an epic has no PR
525
+ line); the questions — open and answered numbers with the `task answer`
526
+ command; then the description as is. Read-only, one GraphQL query (plus the
527
+ recent PRs when none is linked).
528
+
529
+ ### task answer
530
+
531
+ The answer at question `k` of the questions section, not
532
+ a rewrite of the description by hand (`gh issue edit --body-file`): ✅ (with
533
+ `--auto` — the auto mark, `✅ auto` in `en`) after the item's number and
534
+ `**Answer:** …` at its end — on the same line for a one-line item, otherwise
535
+ on a line of its own; est strips the answer from the description size. The
536
+ numbering and the rest of the description don't change; the answer's line
537
+ breaks become spaces. Open questions left — an `○` line with their numbers;
538
+ none — the questions label is removed. Refused before any change (code 2): no
539
+ questions section, no question `k`, the question already has ✅ (a changed
540
+ answer is edited by hand), an empty answer.
541
+
542
+ ### task status
543
+
544
+ Sets `Status` (a task outside the project is added). A
545
+ subtask going In progress moves its epic from Backlog to In progress too.
546
+ Hints: the epic is closed but a subtask is in work again — reopen the epic and
547
+ its milestone; all subtasks are closed — close the epic and set it to Done.
548
+ In progress pins the task to the session: the file
549
+ `~/.config/elves/sessions/<CLAUDE_CODE_SESSION_ID>` (or in
550
+ `$ELVES_CONFIG_DIR`) with the line `owner/repo#N`. Another task In progress in
551
+ the same session — a refusal (code 2) before any changes, in one line: the
552
+ pinned task, the question to the owner «queue #M?» with its executor's prompt
553
+ (as `task new` above) and the file path. The same task again, an epic, Backlog
554
+ and Done — no refusal; a task handed out to an executor of this session (`task
555
+ queue --assignment`, below) — neither a pin nor a refusal, an `○` line: the
556
+ executor is a subagent with the session's id. No command removes the pin:
557
+ a mistaken one is removed by a human, by deleting the file. A line not of the
558
+ form `owner/repo#N` isn't a pin — for `est` and the dashboard either
559
+ (`tests/standards/session-pin`): In progress replaces it with the task, an `○`
560
+ line. `est` counts the actual by the pin: the whole session goes to this task.
561
+ No variable (not Claude Code) — the line `○ session not recognized …`, the
562
+ status is set, no guard. A dispatcher session (`task queue` below) — no pin:
563
+ the line `○ dispatcher session: #N isn't pinned …`.
564
+
565
+ ### task status --branch
566
+
567
+ `task status --branch <type>/<N>-<slug>` — only with In progress, not for
568
+ an epic: the current branch of a worktree tool (`claude/<slug>-<hash>`)
569
+ becomes the task branch together with its section in `.git/config`
570
+ (upstream) — `+ branch <old> → <new>`. The name is checked by the canon before
571
+ any changes (`[<area>/]<type>/<N>-<slug>`, the slug — Latin kebab-case),
572
+ otherwise code 2. `.git/config` is shared by all worktrees of the repository,
573
+ and another checkout's git holds its lock: a bare `git branch -m` renames the
574
+ ref, not the section, with code 128, and its repeat no longer finds the old
575
+ name (#438). The script repeats the step that failed on the lock (~10 s);
576
+ still locked — a `!` line with the lock file, its age and the command that
577
+ finishes the rename, code 1; the lock isn't removed — another process holds
578
+ it. Already on the task branch — `=`; a branch already on origin (`main`, a
579
+ pushed one), a detached HEAD, a directory that isn't a checkout of the
580
+ repository — an `○` line, the branch is left alone.
581
+
582
+ ### pr labels
583
+
584
+ Decision labels from the PR's changed files (API):
585
+ `tests/capabilities|standards|architecture/<name>/…` — a decision label, a code
586
+ file — the label of the model module from the PR head (the longest matching
587
+ directory), `tests/lib` and files outside modules aren't decisions. A
588
+ mechanical edit doesn't change a decision and gives no label: a deleted file,
589
+ a rename without content changes, an exceptions file of a decision folder that
590
+ spec reads as an exception (`exceptions/*.json`, the former `exceptions.*`; a
591
+ copy of the spec rule `exceptionFile`) — a decision touched only that way
592
+ comes out as an `○` line (a label is needed — set it by hand). A decision whose
593
+ name belongs to a slice label (module `web` and the app label `web`) — also an
594
+ `○` line: a slice label isn't set as a decision label. It labels the tasks from
595
+ "Closes #N" (the PR–task link; right after the PR is created GitHub hasn't set
596
+ it yet — the tasks from the PR body's keywords as GitHub understands them; none
597
+ at all — an error), creates a missing label with the kind's color; it doesn't
598
+ remove an earlier label of a decision missing from the diff — an `=` line. The
599
+ task's epic gets the same labels — the union of its subtasks; same with
600
+ `task new --epic --labels`. GitHub puts at most 100 labels on an issue, and a
601
+ mutation past that fails as a whole (#569): the room is 100 minus the labels
602
+ the task (the epic) already has. Everything fits — everything is set; it
603
+ doesn't — only the decisions whose statements the PR changes: the folder's
604
+ `<name>.md` edited in content or the folder named in the spec section of the PR
605
+ body (spec-diff output — the lists, the summary, the move, harness checks;
606
+ exceptions don't count), alphabetically, as many as fit. A codemod over every
607
+ test changes no statement: test bodies and code only — an `○` line without a
608
+ label, what didn't fit — an `○` line too; code 0. `task new --epic --labels`
609
+ has no PR yet to choose by (#574): the epic gets the task's decisions
610
+ alphabetically, as many as fit, the rest — `○` lines, code 0 — the task is
611
+ already created by then.
612
+
613
+ ### pr premerge
614
+
615
+ Before merging a green PR, from a repository checkout: CI
616
+ checked the merge with the default branch as it was at the time of the run,
617
+ and without a merge queue the branch moves ahead. Didn't move — `✅ merge`,
618
+ nothing is built. Moved — the PR head (fetched by its SHA, not by
619
+ `refs/pull/<N>/head`: GitHub creates that ref up to minutes late, #505) merged
620
+ with a fresh `origin/<base>` in a temporary worktree (`git merge`, like
621
+ GitHub's merge-ref; the worktree is always removed), dependencies by the
622
+ merge's lockfile (the manager — `packageManager`, otherwise the lockfile,
623
+ otherwise `.yarnrc.yml` — Yarn, otherwise npm,
624
+ `tests/standards/package-manager`; the install —
625
+ `bun install --frozen-lockfile`, `pnpm install --frozen-lockfile`,
626
+ `yarn install --frozen-lockfile`, `npm ci`; no lockfile — no install) and the
627
+ project's fast checks — the `package.json` scripts that exist, in CI order
628
+ until the first red: `typecheck` (a semantic conflict of two PRs: one removed a
629
+ function, the other calls it — seconds, #292), then `test:spec` — standards and
630
+ architecture, where two green PRs break ("registry + invariant"), not the
631
+ whole `test`. None of them — an `○` line, merge. The merge has `tests/standards`
632
+ or `tests/architecture` but no `test:spec` — an `○` line that they aren't
633
+ checked on the merge, with a pointer to the project's `check` (it requires the
634
+ script and names the line, #442); the merge isn't stopped. GitHub doesn't store what CI
635
+ checked, so "moved" is judged by time (REST): the earliest check suite of the
636
+ PR head against the branch's last event (Activity API); an event within a
637
+ minute before the check suite also counts as moved (the merge-ref is built
638
+ earlier); no checks or the event wasn't read — it is checked. Codes: `0` —
639
+ merge; `1` — don't merge: red `typecheck` (compiler errors), `test:spec`
640
+ (lines of failed tests) or a conflict (files) — fix the root cause in the PR:
641
+ rebase onto `origin/<base>`, edit, `push --force-with-lease`, `ci-wait` again;
642
+ `2` — couldn't check (not a checkout, PR not open, the PR head not on origin,
643
+ dependency install failed) —
644
+ don't merge either, investigate; `3` — **`main` is red, not this PR**.
645
+
646
+ ### Red main — code 3
647
+
648
+ The merge is red (#282) — the failed check is also run on bare
649
+ `origin/<base>` in its own temporary worktree: red with the same — code `3`;
650
+ the PR breaks beyond it (failed tests and compiler errors of the merge that
651
+ aren't on `main`, ignoring run time and position in the file) — code `1` with
652
+ them. A red check on the default branch's last commit — a check-run (the last
653
+ run of each name: `failure`, `timed_out`, `startup_failure`; a cancelled one
654
+ isn't red) or a host status (`failure`, `error`) — the same code `3` before
655
+ checking the merge; checks not read — an `○` line, the check goes on. The spec
656
+ publish job (`spec-publish`, `<job> / spec-publish`) isn't a check of `main`:
657
+ red, it is an `○` line and not counted (#443). With
658
+ code `3` the session doesn't merge and doesn't fix it in its PR: one bug for
659
+ everyone — an open issue titled «`<base>` is red…» (REST
660
+ search) that names the failed check (or script) — the whole name: `test` isn't
661
+ in "test:spec" (#338); open ones about another check — the line
662
+ `○ … about another check: don't wait`. Found — a
663
+ line with the number and the wait command `wait-ci.sh issue <N>` (the `ci-wait`
664
+ skill, as a background command: its completion wakes the session), then
665
+ `premerge` again; for a failed check that none of them names — a hint to find
666
+ an open bug on the failed test (under another title) or the command
667
+ `task new --type Bug` with the title
668
+ «`<base>` is red: <check | typecheck | test:spec>» (`--priority Urgent` in an
669
+ organization; add the failed test's decision label), the question to the owner
670
+ whether to queue the bug and the same wait. A PR that closes any open «`<base>` is red…» bug (`Closes #N`
671
+ in the body) is a fix, without matching the check: the same breakage is named
672
+ by a script one time and by a CI check another; a red `main` doesn't stop it
673
+ (an `○` line), its red merge is code `1`, as usual.
674
+
675
+ ### task resume
676
+
677
+ A background wait (`ci-wait`) dies with the app's process,
678
+ and a session resumed after a restart doesn't know it (#433). The command reads
679
+ the task pinned to the session (`task status` above; the repository is in the
680
+ pin, not the checkout) and its PRs — linked, otherwise by the task branch, as in
681
+ `task close`: an open PR — `wait-ci.sh pr <PR>` as a background command; only a
682
+ merged one and the task is open — `wait-ci.sh merged <PR>` where the merge
683
+ deploys, then `task close N`; no PR or the task is closed — an `○` line; no pin
684
+ — nothing, without a request to GitHub; a line not of the form `owner/repo#N` —
685
+ an `○` line with the file path, code 0. Read-only. The Claude Code
686
+ `SessionStart` hook runs it with the session id from its input (`check
687
+ --hook`, `claude/CLAUDE.md`) — the machine's skill, otherwise the project's. In a
688
+ dispatcher session — or one with its own task and executors (#546) — it first
689
+ says to continue the stopped executors (#528):
690
+ they stopped with the app's process, their transcripts are intact, and the
691
+ app names them in its notification — each is continued with `SendMessage` by
692
+ its id, with its context in its own worktree (the task branch, uncommitted
693
+ work), then the queue command, if the session ran one; the hand-outs
694
+ (`dispatchers/<id>.tasks`, "task queue") stay; then its own task's wait. A new executor — only for one that can't be continued, with the
695
+ same prompt: a task In progress it continues in the worktree of its branch
696
+ (the assignment's restart line), not in a new one — git won't give the branch
697
+ to a second worktree.
698
+
699
+ ### task queue
700
+
701
+ A dispatcher: "do all tasks of epic #M", "fix all
702
+ High bugs", the owner's "yes" to «run the dispatcher on epic #M?» (`AGENTS.md`,
703
+ "Session and branch — one task") — any session: one with its own task keeps it
704
+ pinned, its task is `=` in the queue (#546). The script prints the queue — ready tasks (open, not an epic, not in
705
+ work, no open blockers — another repository's named `owner/repo#N` —, no
706
+ `external blocker` label — its line has the reason from the latest
707
+ `External blocker: …` comment) by `Priority`, then number, each with an indented
708
+ line — its executor's prompt, «Task #N …» in the project language (`est` links
709
+ the subagent's work by it); then `=` lines — in work: In progress, an open PR
710
+ (linked, or by the task branch and "Closes #N" among recent PRs) with its link,
711
+ handed out to an executor of this session; `○` lines — not ready
712
+ with the reason, and `Next:`. Before the queue, in the repository's checkout,
713
+ it removes what finished executors left (#515): an agent's worktree
714
+ (`.claude/worktrees/agent-*`) detached or on a `worktree-agent-*` branch, not
715
+ locked by a live process (Claude Code locks a running agent's worktree; a lock
716
+ whose pid is gone doesn't hold), clean and without commits outside
717
+ `origin/<base>` by `git cherry` — removed with its branch, a `+` line;
718
+ uncommitted changes or unmerged commits — left, an `○` line with the reason; a
719
+ `worktree-agent-*` branch without a worktree — the same by its commits. A
720
+ session's worktree, an agent's on a task branch and an executor's whose
721
+ handed-out task is open — untouched, no line (#533): Claude Code unlocks the
722
+ worktree when the agent ends its answer, a question too, and doesn't lock it
723
+ again for an executor continued with `SendMessage` after a restart. An agent's
724
+ worktree a subagent of it works in without a worktree of its own — left, an
725
+ `○` line naming the subagent (#571: the executor closed its task and answered,
726
+ its epic refresh still ran in its directory, and the queue removed it): such a
727
+ subagent — `parentAgentId` without its own `worktreePath` in Claude Code's
728
+ subagent metadata of this session and of the sessions with hand-outs
729
+ (`dispatchers/*.tasks`), at any depth; at work — its transcript
730
+ (`agent-<id>.jsonl`) written in the last 15 minutes.
731
+ A session started to dispatch works in the project folder, not a worktree
732
+ (#588): the app starts a cloud session from the branch in the starting
733
+ session's record (`get_session` → `branch`), not from git — a session from
734
+ the app's worktree has its `claude/<slug>-<hash>` there, never pushed, and the
735
+ clone fails with «The requested branch or commit was not found», a detached
736
+ checkout too; a session in the project folder has none — `main`. In the app's
737
+ worktree (`.claude/worktrees/<name>`, not `agent-*`) the queue prints a `!`
738
+ line with the project folder and still goes, code 0: subagent executors
739
+ branch from origin/main in their own worktrees.
740
+ `--epic N` — the epic's open sub-issues,
741
+ otherwise the repository's open tasks (the first 100 of the search);
742
+ `--type`, `--priority` — only in an organization. It marks the session as a
743
+ dispatcher — `dispatchers/<CLAUDE_CODE_SESSION_ID>` in the configuration
744
+ directory with the queue command: the executors are subagents with the
745
+ session's id, a pin would refuse the second and give `est` the whole session as
746
+ one task. `--assignment N` —
747
+ read-only on GitHub, the executor's first call; in any session (not one pinned
748
+ to `N`: that task is its own) it records the task as handed out — a line
749
+ `owner/repo#N <worktree>` (the checkout the assignment is called in) in
750
+ `dispatchers/<id>.tasks`: a session with its own task, or none yet, launches an
751
+ executor on the owner's "yes" too (#546), and unrecorded, the executor's In
752
+ progress would be refused by the session's pin or would pin the session to its
753
+ task. `task status` doesn't pin a handed-out task; `est` gives a subagent with
754
+ «Task #N» in its assignment to task N, not to the session's pin. A hand-out is
755
+ `=` in the queue for the whole session (an
756
+ executor's answer leaves the task not ready either: merged closes it, a question
757
+ waits for the owner, waits for subagents — the same executor answers again,
758
+ failed isn't restarted; after the app restarts the executor
759
+ is continued, "task resume"). The assignment: the task by the canon in auto mode, a
760
+ restart line only for an In progress task with a branch `*/N-*` in the
761
+ checkout or on its origin (it names the branch; the branch checked out in
762
+ another worktree — work there, in Claude Code `EnterWorktree` with its path,
763
+ not in a new one), and what a subagent does
764
+ otherwise — doesn't rename or archive the session and writes no last line,
765
+ waits for CI and the deploy with regular calls
766
+ (`wait-ci.sh … --timeout 540`: a background command doesn't wake a subagent, a
767
+ Bash call lives 10 minutes), git in separate calls, writing subagents — as
768
+ needed, without a limit (the limit of 3 is on executors), each in its own
769
+ worktree with «Task #N» at the start of its prompt, their worktrees and
770
+ branches removed by the executor after the merge, no answer while any of its
771
+ subagents works (#565: an answer with live subagents reads as a failure — the
772
+ executor of #545 answered failed, its subagents' answers woke it, and it merged
773
+ the task while a second executor, launched on the owner's restart, worked on the
774
+ same branch), found work — `task new`,
775
+ named in the answer (whether to queue it is the owner's call); the answer — one
776
+ line: merged, a question, waits for subagents (it can't wait — not a failure) or
777
+ failed.
778
+
779
+ A restart is a new executor on a task handed out before or on a task branch:
780
+ its `--assignment` refuses (code 1, no hand-out) while the previous executor or
781
+ its subagent is alive — `!` lines with each live worktree, and the answer
782
+ «waits for the previous executor». The previous executor's worktrees — its
783
+ hand-outs of the task in any session's `dispatchers/*.tasks` and a worktree on
784
+ the task branch, the caller's own aside; its subagents' (`agent-<id>`) — by
785
+ `parentAgentId` of Claude Code's subagent metadata
786
+ (`projects/<directory>/<session id>/subagents/agent-<id>.meta.json` in its
787
+ configuration directory), at any depth. Alive — locked by a live process, a
788
+ subagent without a worktree of its own at work in it (above), or HEAD moved (a commit, a switch) in
789
+ the last 15 minutes: Claude Code doesn't lock the worktree of an agent continued
790
+ after its answer, and the woken executor of #545 worked unlocked; the wait for CI
791
+ is a Bash call of up to 9 minutes without commits.
792
+
793
+ In Claude Code: an executor — `Agent` with `isolation: "worktree"` and
794
+ `run_in_background: true`, the prompt — the indented line as is; at most 3
795
+ subagents and 5 cloud sessions at once, the owner's number replaces either
796
+ (subagents share the machine, `slot` runs two heavy checks; a cloud session
797
+ has its own container); a task in work (`=`) isn't launched again.
798
+ Before each launch — `get_usage`: the 5-hour limit at 85% and more — no new
799
+ executors, the running ones finish, then `Remaining: N tasks, the limit`. An
800
+ answer arrives as the subagent's notification; after each — `task queue`
801
+ again. A question — to the owner in text; the answer — ✅ in the task's
802
+ questions section and to the same executor with `SendMessage` (its context is
803
+ intact). Waits for subagents — no restart: its subagents' answers wake it, and it
804
+ answers again. Failed — a comment in the task with what stopped, no restart; the
805
+ owner's restart — the refused assignment's «waits for the previous executor» —
806
+ wait for the previous executor's answer, not a third executor; failed
807
+ on an external blocker — it's recorded ("Dependencies" above), and the queue
808
+ stops giving the task out until it's lifted. Queue
809
+ empty and nothing running — the summary (merged, questions, not ready, new
810
+ tasks outside the queue), the last line and, in auto mode, archiving; a session
811
+ with its own task — after it. A running executor isn't stopped (`TaskStop`) to
812
+ archive the session: its answer comes first. The app's
813
+ child sessions (`start_session`) aren't given out yet (a server flag); when
814
+ they are, an executor becomes a session. A cloud executor (claude.ai/code,
815
+ #609) — `--assignment N --cloud`, called by the dispatcher itself: the brief,
816
+ the cloud session's first prompt as is — whole, `task queue` refuses in
817
+ the cloud; `#` only at its task (`docs/cloud-sessions.md`, "The
818
+ brief"); after the merge — the cloud's close, without messaging the
819
+ dispatcher or archiving its session. The hand-out — without a worktree
820
+ (`@cloud`). A cloud session can't message a local one, so `Next:` gives the
821
+ dispatcher the wait for the task's closing — `wait-ci.sh issue N` as a
822
+ background command; PASS — `task close N`, then archiving the executor's
823
+ session; TIMEOUT — the task's questions and State comment, the wait again;
824
+ after the app restarts the queue's `=` line names the wait.
825
+
826
+ ### task drop
827
+
828
+ Closes as not planned (with `--duplicate-of` — as a
829
+ duplicate with a link) and removes it from the project: otherwise "Item closed"
830
+ would put it in Done and into the epic's estimate. It doesn't touch a completed
831
+ one. A subtask — a hint to recalculate the epic's estimate.
832
+
833
+ ### task actualize
834
+
835
+ The epic refresh, right after the merge: it doesn't
836
+ depend on the deploy and runs in parallel with it, while the session at peak
837
+ context neither assembles the assignment nor reads `reference.md`. The
838
+ mechanics — itself, `+` lines (#435: the subagent collected it with ~35 calls
839
+ a run): blocked by on done tasks (closed, or this one with its PR merged) is
840
+ removed, the `questions` label without open questions in the section, a
841
+ milestone of the epic's tasks without open tasks is closed (the task's and the
842
+ epic's — by `task close`); the fixes — one line in a comment on the epic. Then
843
+ the subagent's prompt — the two lines after `Next:`, pass them as is: «Task #N
844
+ (epic #M)» (`subagentPrompt` of the dictionary; `est` links the subagent's
845
+ work to the task by it in any language) and the command of its whole
846
+ assignment, `task actualize N --assignment` — read-only, the subagent's first
847
+ call, outside the sandbox: the texts of the epic and its open tasks (its
848
+ sub-issues, not a list in its description) and the PR files run to tens of KB,
849
+ and the session would copy them into the subagent call as output tokens. In
850
+ the assignment, in the project language: the task and its merged PR (the link,
851
+ otherwise the branch or "Closes #N" among recent PRs) with its files, the epic
852
+ and its open tasks with their texts, the copies of links in text and the
853
+ answers to open questions in comments (`k. ✅ …`) the script found — named, not
854
+ edited, the checks by meaning 1, 3 and 5 from section "Epic refresh on task
855
+ close", what may be changed (only GitHub tasks; the task, the epic and their
856
+ milestone are closed by `task close`), the result — always a comment on the
857
+ epic «Refresh after #N, result: …», «the block is current» too (`task close`
858
+ waits for it) — and the answer form. No epic, or no open tasks in the epic other than
859
+ this one — the line `○ refresh not needed`, no subagent. The order after the
860
+ merge — fewer turns (measurement #285: the extra
861
+ turns were `gh pr view` for the SHA and reading the wait result): in one turn,
862
+ as separate calls, — `task actualize N` and a background
863
+ `wait-ci.sh merged <PR>` (where the merge deploys or runs CI on `main`; the
864
+ script takes the merge SHA from the PR itself), in Claude Code — also
865
+ `ToolSearch` for the `archive_session` schema if it isn't loaded yet; next — in
866
+ the background the subagent with the assignment, if a refresh is needed; a wait
867
+ notification with code 0 — `task close N` right away, don't read the output
868
+ file (not 0 — the `RESULT:` line in it); the subagent hasn't reported yet —
869
+ close refuses before any change: close again after its answer; then the last
870
+ line of the answer and archiving — in one turn.
871
+
872
+ ### task close
873
+
874
+ The closing ritual in one call, not a step per turn at the
875
+ session's largest context. Only after the task's PR is merged (or a close
876
+ without a PR): a PR that is open or closed without a merge — an error before
877
+ any changes. GitHub sometimes doesn't link a merged PR to the task for hours —
878
+ then the task's PR is searched among the default branch's recent PRs by the
879
+ branch `<type>/<N>-<slug>` and "Closes #N" in the body: merged — the task is
880
+ closed with the comment «Closed by PR #M: GitHub didn't link the PR to the
881
+ task», none — a refusal
882
+ listing what was checked (no manual `gh issue close` needed). The epic has
883
+ open tasks besides this one and no report of the refresh on it — a comment
884
+ with «Refresh after #N, result:» in any language of the dictionary, case and
885
+ markup aside; the script's own «Refresh after #N, script: …» comes before the
886
+ subagent and isn't it — a refusal before any changes, with what to do: the
887
+ subagent is at work — wait for its answer, it wasn't launched — `task
888
+ actualize N`, it answered without the comment — post it (#571). Order:
889
+ close the task if the merged PR didn't; a task of the scripts' own repository
890
+ (Elves) — the scripts' clone (the checkout the est script is in) catches up
891
+ with the merged `main`: `git pull --ff-only origin main`, the line
892
+ `+ clone <dir> <a> → <b>` (already there — `=`); a dispatcher's executors call
893
+ the clone's scripts, and nothing else updates it during a session; on another
894
+ branch or with uncommitted changes to tracked files — an `○` line, not touched
895
+ (untracked files don't count), a failed pull — a `!` line, the actual is by
896
+ the clone's code; another project's task, or the est script not in a git
897
+ checkout — no step; the actual — `est fact <N> --write` by
898
+ the est script next to it (`../est/scripts/est.ts`; missing — the line
899
+ `○ actual unavailable`, failed — its error as a `!` line, the
900
+ rest is done); `Status` Done; the last sub-issue closed — the epic is closed
901
+ and gets Done; no open tasks left in the task's (and the epic's) milestone —
902
+ the milestone is closed (REST); the task's stacks (the `task-env` skill) —
903
+ `task-env down <N>` by the script next to it (`../task-env/scripts/task-env.ts`;
904
+ missing — the line `○ task stacks: task-env is not installed`) in the
905
+ checkout: the dev and e2e stacks `<project>-<N>` and `<project>-<N>-e2e` go
906
+ with their volumes, its lines (`+`, `○`, `!`) are printed as is; no compose
907
+ file in the checkout — an `○` line, Docker isn't called; a failure — a `!`
908
+ line, the rest is done, and `task-env prune` finds the stack later; the merged
909
+ branches — per the "Right after a PR merge" item of section "Git, PRs and
910
+ merging — full rules" below: the branch of every merged PR of the task —
911
+ linked or found by the branch and "Closes #N", as above: a follow-up fix to a
912
+ merged PR is the same task, and GitHub doesn't link it — and the checkout's
913
+ task branch; the checkout moves off a removed branch to `origin/<base>`
914
+ detached, the local and remote branches are deleted; a branch without a
915
+ merged PR is merged only by `git cherry`; not merged or someone else's — an
916
+ `○` line, nothing is touched. The directory
917
+ isn't a checkout of the repository — neither stacks nor branches are touched
918
+ (an `○` line: both are named by the checkout's `origin`); a checkout — the
919
+ `owner/repo` of its `origin` equals the task's repository, case aside, not a
920
+ URL substring: `elves-tools` isn't a checkout of `elves` (standard
921
+ `origin-repo`, the same for `pr premerge`). The command doesn't delete the
922
+ session's worktree — the `Next` hint; an agent's (`.claude/worktrees/agent-*`,
923
+ a dispatcher's executor) — an `○` line: the dispatcher's next `task queue`
924
+ removes it. `--no-git` — without the git step, the
925
+ stacks still go. The last output line — the end-of-session steps: the last
926
+ line of the answer, one of three
927
+ (wording in the project language — `AGENTS.md`, "Language"):
928
+ `All done. The session can be closed.`; the owner has something to decide —
929
+ `All done, but there are questions: …` with the questions themselves;
930
+ something remains — `Remaining: …`. In auto mode after
931
+ `All done. The session can be closed.` — archiving the session where the agent
932
+ has the tool (the command doesn't read the manifest — the condition is stated
933
+ in words); after the other two lines — no. Information without a decision (an
934
+ observation, a deadline, a warning) isn't a question: the line and archiving
935
+ are the same. A cloud session — a refusal with an explanation
936
+ (`docs/cloud-sessions.md`).
937
+
938
+ ## Git, PRs and merging — full rules
939
+
940
+ Principles — `AGENTS.md`, section "Git, PRs and merging"; here — the full
941
+ rules (the short form — `SKILL.md`, "Git, PRs and merging — mechanics").
942
+ Waiting for checks, the full SHA and a merge that deploys —
943
+ `docs/pr-checks.md`.
944
+
945
+ - **Merge — rebase only:** `gh pr merge <N> --rebase`; other methods in the
946
+ repository are turned off by `project fix` (item 11 of section "What is
947
+ checked"). The branch's commits themselves land in `main` — each a
948
+ conventional commit with `Refs #N`.
949
+ - **Merge without a queue:** a PR is merged as soon as its own CI is green and
950
+ GitHub merges it without a conflict; the branch needn't be up to date —
951
+ GitHub merges a branch that fell behind: the default-branch ruleset
952
+ (required checks, no strict) is set by `github project fix`. Rebase onto
953
+ `origin/main` and `git push --force-with-lease` (not `--force`), with
954
+ permission as usual, — only on a textual conflict or my own changes.
955
+ **Before merging — `github pr premerge <N>`** (after `ci-wait` PASS): if
956
+ `main` moved after the PR's CI — `typecheck` and `test:spec` on the merge
957
+ with a fresh `main`; code `1` — don't merge, fix the root cause; code `3` —
958
+ `main` is red by itself: don't merge and don't fix it in my PR — the
959
+ «`main` is red…» bug, wait for it to close
960
+ (`wait-ci.sh issue <N>`) and `premerge` again. A window remains: an
961
+ incompatibility with a PR merged during the check itself shows up as red CI
962
+ of the next PR (it checks the merge-ref on top of a fresh `main`) — fix the
963
+ root cause. Someone else's branch rule with strict won't merge a branch that
964
+ fell behind — then rebase, not `gh pr merge --admin` (a bypass — only on the
965
+ owner's direct instruction).
966
+ - **Before a push** — `git branch --show-current` (my own branch, not the local
967
+ `main`: after a series of checkouts and rebases a push went past the PR).
968
+ - **Merge in auto mode in Claude Code — as a separate call:**
969
+ `gh pr merge <N> --rebase` — the whole command, without `cd`, a pipeline,
970
+ `;` or `&&`. The app's permission classifier refuses a merge without human
971
+ review; before the classifier it is decided by the narrow rule
972
+ `Bash(gh pr merge *)` in `permissions.allow` of the machine settings
973
+ (`~/.claude/settings.json`), but the rule must match each part of a compound
974
+ command separately: a part without its own rule (`gh pr view`) hands the
975
+ whole command to the classifier. The merge result is this call's exit code:
976
+ `0` — merged. The rule is set by the owner: the agent doesn't widen its own
977
+ permission settings, and the project's `"auto": true` doesn't bind the
978
+ classifier. Refused even as a separate call — there is no rule: I don't retry
979
+ the merge or push it through in another form; `Remaining: merge — from the
980
+ owner` (a rule in the machine settings or a word in chat).
981
+ - **After such a merge `gh pr view` isn't needed:** the merge result is the
982
+ exit code, the merge SHA is taken by `wait-ci.sh merged <N>` (an unmerged PR
983
+ — ERROR), `task close <N>` reads the PR itself and refuses on an unmerged one
984
+ before any changes. `gh pr view` isn't among the app's built-in read-only
985
+ commands — the classifier decides it, and the classifier sees the session's
986
+ calls but not their results or the rule the merge passed by: to it, this is a
987
+ merge without review. If a read is still needed — a narrow query as a
988
+ separate call (`gh pr view <N> --json state`); a wider query right after the
989
+ merge (`--json state,mergedAt,mergeCommit`) was refused as
990
+ `[Merge Without Review]`, its retry — as `[Auto-Mode Bypass]`: after a
991
+ refusal I don't repeat the command or get the same thing in another form —
992
+ `Remaining: … — from the owner` (their word in chat or a rule for the command
993
+ in `permissions.allow` of the machine settings).
994
+ - **Right after a PR merge — I delete what was merged entirely, without
995
+ asking** (a `github task close` step): the session's checkout off the merged
996
+ branch —
997
+ `git fetch --prune && git switch --detach origin/main && git branch -D <branch>`,
998
+ the remote branch — `git push origin --delete <branch>` if the host didn't
999
+ delete it, the task's Compose stacks with their volumes (`task-env down
1000
+ <N>`), my own worktrees and the session's other temporary resources.
1001
+ "Merged" — by the PR (`gh pr view <N> --json state` = `MERGED`) or
1002
+ `git cherry origin/main <branch>`, not `git branch --merged`: after a rebase
1003
+ merge the commits in `main` have different SHAs, git doesn't see that the
1004
+ branch is merged — the session panel and `git status` call for a second PR,
1005
+ and a push from it would resurrect the deleted remote branch. I don't touch
1006
+ others' branches or worktrees of live sessions, unmerged work — never. The
1007
+ next task — not in this session.
1008
+ - `gh pr merge --delete-branch` from a worktree doesn't delete the local branch
1009
+ (an old gh fails with `'main' is already used by worktree`) — this isn't a
1010
+ merge error: I check `gh pr view <N> --json state`, I don't repeat the merge.
1011
+
1012
+ ## Manual commands
1013
+
1014
+ What the skill's commands don't cover: editing an already created task and
1015
+ milestones.
1016
+
1017
+ ### Issue types
1018
+
1019
+ In an organization `project fix --confirm` checks and configures the types
1020
+ (Task, Bug and Epic named in the project language, Feature disabled; needs the
1021
+ `admin:org` scope). A personal account has no types — an epic is marked with
1022
+ the `epic` label.
1023
+
1024
+ ```bash
1025
+ gh api -X PATCH repos/{owner}/{repo}/issues/<N> -f type=Bug # change the type
1026
+ gh issue list --json number,issueType --jq '.[]|select(.issueType.name=="Epic")'
1027
+ ```
1028
+
1029
+ ### Priority on an existing task (organization)
1030
+
1031
+ ```bash
1032
+ # ids of the field and its options
1033
+ gh api graphql -f query='{organization(login:"<org>"){issueFields(first:10){nodes{... on IssueFieldSingleSelect{id name options{id name}}}}}}'
1034
+ # set it; issueId — the issue's node_id
1035
+ gh api graphql -f query='mutation{setIssueFieldValue(input:{issueId:"<node_id>",issueFields:[{fieldId:"<field id>",singleSelectOptionId:"<option id>"}]}){issue{number}}}'
1036
+ # read: issue.issueFieldValues{nodes{... on IssueFieldSingleSelectValue{field{... on IssueFieldSingleSelect{name}} name}}}
1037
+ ```
1038
+
1039
+ ### Subtasks and dependencies on an existing task
1040
+
1041
+ ```bash
1042
+ SUB_ID=$(gh api repos/{owner}/{repo}/issues/<subtask> --jq .id) # id, not the number!
1043
+ gh api repos/{owner}/{repo}/issues/<epic>/sub_issues -X POST -F sub_issue_id=$SUB_ID
1044
+ gh api repos/{owner}/{repo}/issues/<epic>/sub_issues # list of subtasks
1045
+ BY_ID=$(gh api repos/{owner}/{repo}/issues/<blocker> --jq .id) # id, not the number!
1046
+ gh api repos/{owner}/{repo}/issues/<N>/dependencies/blocked_by -X POST -F issue_id=$BY_ID
1047
+ gh api repos/{owner}/{repo}/issues/<N>/dependencies/blocked_by # who blocks N
1048
+ gh api repos/{owner}/{repo}/issues/<N>/dependencies/blocking # whom N blocks
1049
+ gh api -X DELETE repos/{owner}/{repo}/issues/<N>/dependencies/blocked_by/$BY_ID
1050
+ ```
1051
+
1052
+ ### Milestones
1053
+
1054
+ ```bash
1055
+ gh api -X GET repos/{owner}/{repo}/milestones -f state=all # list
1056
+ gh api repos/{owner}/{repo}/milestones -f title="Billing · 1 · Design" -f description="Epic: #42"
1057
+ gh issue edit <N> --milestone "Billing · 1 · Design"
1058
+ gh api -X PATCH repos/{owner}/{repo}/milestones/<number> -f state=closed
1059
+ ```
1060
+
1061
+ ### Numeric project fields
1062
+
1063
+ `Estimate, h`, `Actual, h`, `Tokens, M`, `Cost, $` are set only by the
1064
+ `est` script (`github task new` and `task close` run it). By hand — only an epic's `Estimate, h`, the sum of its subtasks'
1065
+ estimates:
1066
+
1067
+ ```bash
1068
+ gh project item-edit --id <item-id> --project-id <project-id> --field-id <estimate-field-id> --number 4.5
1069
+ ```
1070
+
1071
+ Project, field and option IDs — `gh project view` / `gh project field-list`
1072
+ (in the JSON of `gh project item-list` the keys of Cyrillic-named fields are
1073
+ garbled — don't rely on them; `est` finds fields by exact name via GraphQL).