@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.
- package/AGENTS.md +310 -0
- package/README.md +344 -0
- package/bin/ai-dev.ts +7 -0
- package/bin/elves.ts +2847 -0
- package/bin/shims/timeout +113 -0
- package/claude/CLAUDE.md +71 -0
- package/dist/github/scripts/github.js +3428 -0
- package/dist/spec/scripts/architecture.js +628 -0
- package/dist/spec/scripts/eslint-config.js +54 -0
- package/dist/spec/scripts/harness-qzb932c8.js +959 -0
- package/dist/spec/scripts/harness-v9s1v9w3.js +16045 -0
- package/dist/spec/scripts/harness.js +53 -0
- package/dist/spec/scripts/knip-hints.cjs +34 -0
- package/docs/ci-concurrency.md +82 -0
- package/docs/cloud-sessions.md +203 -0
- package/docs/judgment.md +81 -0
- package/docs/parallel-checkouts.md +220 -0
- package/docs/pr-checks.md +117 -0
- package/docs/testing.md +304 -0
- package/package.json +70 -1
- package/skills/ci-wait/SKILL.md +180 -0
- package/skills/ci-wait/scripts/wait-ci.sh +377 -0
- package/skills/dashboard/SKILL.md +212 -0
- package/skills/dashboard/scripts/dashboard.ts +912 -0
- package/skills/dashboard/scripts/sessions.ts +772 -0
- package/skills/est/SKILL.md +221 -0
- package/skills/est/reference.md +381 -0
- package/skills/est/scripts/est.ts +4854 -0
- package/skills/github/SKILL.md +206 -0
- package/skills/github/locales/en.json +449 -0
- package/skills/github/locales/ru.json +449 -0
- package/skills/github/reference.md +1073 -0
- package/skills/github/scripts/github.ts +4390 -0
- package/skills/github/scripts/locale.ts +495 -0
- package/skills/github/scripts/package.json +3 -0
- package/skills/slot/SKILL.md +145 -0
- package/skills/slot/scripts/package.json +3 -0
- package/skills/slot/scripts/slot.ts +279 -0
- package/skills/spec/SKILL.md +247 -0
- package/skills/spec/canon.md +219 -0
- package/skills/spec/checks.md +709 -0
- package/skills/spec/ci.md +378 -0
- package/skills/spec/migration.md +125 -0
- package/skills/spec/reference.md +889 -0
- package/skills/spec/scripts/architecture.ts +839 -0
- package/skills/spec/scripts/c4.ts +184 -0
- package/skills/spec/scripts/eslint-config.ts +82 -0
- package/skills/spec/scripts/harness.ts +1483 -0
- package/skills/spec/scripts/knip-hints.cjs +34 -0
- package/skills/spec/scripts/knip-hints.d.cts +17 -0
- package/skills/spec/scripts/package-run.ts +22 -0
- package/skills/spec/scripts/package.json +3 -0
- package/skills/spec/scripts/spec-break.ts +232 -0
- package/skills/spec/scripts/spec-claims.ts +198 -0
- package/skills/spec/scripts/spec-diff.ts +1111 -0
- package/skills/spec/scripts/spec-doc.ts +744 -0
- package/skills/spec/scripts/spec-exceptions.ts +233 -0
- package/skills/spec/scripts/spec-publish.ts +214 -0
- package/skills/spec/scripts/spec-run.ts +235 -0
- package/skills/spec/scripts/spec-shard.ts +267 -0
- package/skills/spec/scripts/speclib.ts +826 -0
- package/skills/spec/scripts/vendor/babel-parser.LICENSE +19 -0
- package/skills/spec/scripts/vendor/babel-parser.cjs +14615 -0
- package/skills/spec/scripts/vendor/babel-parser.d.cts +3 -0
- package/skills/task-env/SKILL.md +231 -0
- package/skills/task-env/proxy/compose.yaml +51 -0
- package/skills/task-env/reference.md +254 -0
- 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).
|