@miroshnik/elves 2026.1019.1 → 2026.1019.2
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 +8 -7
- package/dist/github/scripts/github.js +21 -7
- package/dist/spec/scripts/architecture.js +2 -2
- package/dist/spec/scripts/{harness-ay62ws50.js → harness-bpyj4s91.js} +1 -1
- package/dist/spec/scripts/{harness-wqrt5gpa.js → harness-tdg4dnq3.js} +14 -4
- package/dist/spec/scripts/harness.js +2 -2
- package/package.json +2 -2
- package/skills/github/locales/en.json +7 -2
- package/skills/github/locales/ru.json +7 -2
- package/skills/github/reference.md +14 -6
- package/skills/github/scripts/github.ts +31 -10
- package/skills/github/scripts/locale.ts +10 -0
package/AGENTS.md
CHANGED
|
@@ -199,9 +199,9 @@ it.
|
|
|
199
199
|
PR's CI — fast checks on the merge with it; red — I fix the root cause;
|
|
200
200
|
`main` red by itself (exit code 3) — no merge and no fix in the PR: one bug
|
|
201
201
|
for it, I wait for it to close.
|
|
202
|
-
- A merge that deploys — I wait for the deploy on the merge SHA (`ci-wait`)
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
- A merge that deploys — I wait for the deploy on the merge SHA (`ci-wait`)
|
|
203
|
+
**before `github task close`**. Others' branches, stacks and unmerged work
|
|
204
|
+
I don't touch.
|
|
205
205
|
- Mechanics — the `github` skill, "Git, PRs and merging — mechanics";
|
|
206
206
|
waiting for checks and deploys — `docs/pr-checks.md`.
|
|
207
207
|
|
|
@@ -222,8 +222,9 @@ folder's `exceptions/`, with an `#issue`, a reason and a ratchet. Test names
|
|
|
222
222
|
are statements in the project language; a code task gets the scenarios
|
|
223
223
|
section (future `it` names) before code. The PR body is `spec-diff` output;
|
|
224
224
|
docs are generated (`spec-doc`) and published by CI to the `spec` branch. A
|
|
225
|
-
recurring decision — a "Standard · …" issue and a rule in `tests/standards
|
|
226
|
-
The full canon and wiring — the `spec`
|
|
225
|
+
recurring decision — a "Standard · …" issue and a rule in `tests/standards/`;
|
|
226
|
+
repeats in a PR's files — fixed in it. The full canon and wiring — the `spec`
|
|
227
|
+
skill (`canon.md`, `checks.md`).
|
|
227
228
|
|
|
228
229
|
## Tests and code
|
|
229
230
|
|
|
@@ -282,8 +283,8 @@ The full canon and wiring — the `spec` skill (`canon.md`, `checks.md`).
|
|
|
282
283
|
skill) at `<service>.<N>.<project>.localhost`, not on a host port; others'
|
|
283
284
|
stacks and busy ports I don't touch — `docs/parallel-checkouts.md`.
|
|
284
285
|
- **A subagent gets a unit of ~50 turns** (one decision's folder, one file,
|
|
285
|
-
one spec), not a 200-turn zone:
|
|
286
|
-
|
|
286
|
+
one spec), not a 200-turn zone: with the same ≈ 100k-token preamble its
|
|
287
|
+
price is quadratic in turns. The assignment has the task
|
|
287
288
|
number after the word for "task" or at the start, the epic after the word
|
|
288
289
|
for "epic" ("Language") — so `est` links its work. **Fewer turns:** a file
|
|
289
290
|
in one full read, independent tool calls in one turn.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import {
|
|
3
3
|
locale_default
|
|
4
|
-
} from "../../spec/scripts/harness-
|
|
4
|
+
} from "../../spec/scripts/harness-tdg4dnq3.js";
|
|
5
5
|
|
|
6
6
|
// skills/github/scripts/github.ts
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -1787,11 +1787,16 @@ function cloudBrief(io, slug, issue) {
|
|
|
1787
1787
|
state: N.comments.state,
|
|
1788
1788
|
questions: N.sections.questions
|
|
1789
1789
|
};
|
|
1790
|
-
for (const line of [N.queue.cloud, N.queue.cloudDoc, N.queue.cloudClose, N.queue.cloudDispatcher, N.queue.cloudQuestion])
|
|
1790
|
+
for (const line of [N.queue.cloud, N.queue.cloudDoc, N.queue.repeats, N.queue.cloudClose, N.queue.cloudDispatcher, N.queue.cloudQuestion])
|
|
1791
1791
|
io.out(locale_default.fill(line, vars));
|
|
1792
1792
|
io.out(`Next: the lines above — the cloud session's first prompt as is, the session started from the project folder (the cloud starts from main); ` + `then as a background command: bash ${waitCi()} issue ${number} — a cloud session can't message a local one (docs/cloud-sessions.md), the task's closing is the signal: ` + `PASS — bun --no-env-file ${import.meta.path} task close ${number} (the actual with --write, ${STATUS}, the merged branch), then archive the executor's session in an auto project where the agent has the tool; ` + `TIMEOUT — the task's ${q(N.sections.questions)} and its ${q(N.comments.state)} comment, the executor's session, then the same wait again`);
|
|
1793
1793
|
}
|
|
1794
1794
|
var SESSION_ARCHIVE = "an executor session whose task closed — the dispatcher archives it in an auto project where the agent has the tool, " + "not one still working in the background or awaiting the owner's answer; outside auto the owner closes it";
|
|
1795
|
+
function openPrOf(io, slug) {
|
|
1796
|
+
const [owner, name] = slug.split("/");
|
|
1797
|
+
const recent = (graphql(io, Q.RecentPrs, { owner, name })?.repository?.pullRequests?.nodes ?? []).filter((p) => p.state === "OPEN");
|
|
1798
|
+
return (i) => i.prs.find((p) => p.state === "OPEN")?.number ?? recent.find((p) => isTaskBranch(p.headRefName ?? "", i.number) || closingInBody(p.body ?? "").includes(i.number))?.number;
|
|
1799
|
+
}
|
|
1795
1800
|
function cmdTaskQueue(io, slug, o) {
|
|
1796
1801
|
const want = o.type === undefined ? undefined : typeAliases()[o.type.toLowerCase()];
|
|
1797
1802
|
if (o.type !== undefined && (!want || want === N.issueTypes.epic))
|
|
@@ -1817,9 +1822,7 @@ function cmdTaskQueue(io, slug, o) {
|
|
|
1817
1822
|
io.out("○ the search gave 100 open tasks — the queue sees only them: narrow it with --epic");
|
|
1818
1823
|
numbers = found.filter((x) => x?.state === "OPEN").map((x) => x.number);
|
|
1819
1824
|
}
|
|
1820
|
-
const
|
|
1821
|
-
const recent = (graphql(io, Q.RecentPrs, { owner, name })?.repository?.pullRequests?.nodes ?? []).filter((p) => p.state === "OPEN");
|
|
1822
|
-
const openPr = (i) => i.prs.find((p) => p.state === "OPEN")?.number ?? recent.find((p) => isTaskBranch(p.headRefName ?? "", i.number) || closingInBody(p.body ?? "").includes(i.number))?.number;
|
|
1825
|
+
const openPr = openPrOf(io, slug);
|
|
1823
1826
|
const handed = handedOut(io, slug);
|
|
1824
1827
|
const states = new Map;
|
|
1825
1828
|
const isOpen = (n) => {
|
|
@@ -2160,7 +2163,7 @@ function cmdTaskAssignment(io, slug, number, o = {}) {
|
|
|
2160
2163
|
remaining: N.lastLine.remaining
|
|
2161
2164
|
};
|
|
2162
2165
|
const resume = restart.length ? [N.queue.resume] : [];
|
|
2163
|
-
const lines = o.session ? [N.queue.sessionLead, ...resume, N.queue.sessionIs, N.queue.sessionName, N.queue.sessionWait, N.queue.sessionFound, N.queue.sessionQuestion, N.queue.sessionEnd] : [N.queue.lead, ...resume, N.queue.subagent, N.queue.session, N.queue.wait, N.queue.git, N.queue.subagents, N.queue.live, N.queue.refresh, N.queue.found, N.queue.question, N.queue.answer];
|
|
2166
|
+
const lines = o.session ? [N.queue.sessionLead, ...resume, N.queue.sessionIs, N.queue.sessionName, N.queue.sessionWait, N.queue.repeats, N.queue.sessionFound, N.queue.sessionQuestion, N.queue.sessionEnd] : [N.queue.lead, ...resume, N.queue.subagent, N.queue.session, N.queue.wait, N.queue.git, N.queue.subagents, N.queue.live, N.queue.refresh, N.queue.repeats, N.queue.found, N.queue.question, N.queue.answer];
|
|
2164
2167
|
for (const line of lines)
|
|
2165
2168
|
io.out(locale_default.fill(line, vars));
|
|
2166
2169
|
if (o.session) {
|
|
@@ -2307,6 +2310,7 @@ function cmdTaskActualize(io, slug, number, o = { assignment: false }) {
|
|
|
2307
2310
|
refreshMechanics(io, slug, { number, merged: prs.length > 0, epic, open });
|
|
2308
2311
|
if (!open.length)
|
|
2309
2312
|
return io.out(`○ refresh not needed: epic #${epic.number} has no open tasks besides #${number}. ${later}`), 0;
|
|
2313
|
+
const ctx = o.assignment ? taskContext(io, slug) : null;
|
|
2310
2314
|
const vars = { task: N.subagentPrompt.task, epicWord: N.subagentPrompt.epic, number, epic: epic.number, repo: slug };
|
|
2311
2315
|
const lead = locale_default.fill(N.actualize.lead, vars);
|
|
2312
2316
|
if (!o.assignment) {
|
|
@@ -2324,8 +2328,18 @@ function cmdTaskActualize(io, slug, number, o = { assignment: false }) {
|
|
|
2324
2328
|
lines.push(locale_default.fill(N.actualize.files, { pr: p.number }), ...files.map(fileLine));
|
|
2325
2329
|
}
|
|
2326
2330
|
lines.push("", locale_default.fill(N.actualize.epic, { ...vars, title: epic.title }), textBlock(epic.body), "", N.actualize.openTasks);
|
|
2331
|
+
const openPr = openPrOf(io, slug);
|
|
2332
|
+
const target = (n) => {
|
|
2333
|
+
const i = loadIssue(io, slug, n);
|
|
2334
|
+
const status = itemIn(ctx, i)?.status ?? null;
|
|
2335
|
+
const pr = openPr(i);
|
|
2336
|
+
const work = [status === N.status.inProgress ? locale_default.fill(N.actualize.taskStatus, { status }) : "", pr === undefined ? "" : locale_default.fill(N.actualize.taskPr, { pr })].filter(Boolean);
|
|
2337
|
+
if (work.length)
|
|
2338
|
+
return [locale_default.fill(N.actualize.inWork, { work: work.join(", ") })];
|
|
2339
|
+
return status === N.status.backlog ? [locale_default.fill(N.actualize.backlog, { status: locale_default.fill(N.actualize.taskStatus, { status }) })] : [];
|
|
2340
|
+
};
|
|
2327
2341
|
for (const t of open)
|
|
2328
|
-
lines.push("", locale_default.fill(N.actualize.openTask, { number: t.number, title: t.title }), textBlock(t.body));
|
|
2342
|
+
lines.push("", locale_default.fill(N.actualize.openTask, { number: t.number, title: t.title }), ...target(t.number), textBlock(t.body));
|
|
2329
2343
|
const copies = linkCopies(epic, open);
|
|
2330
2344
|
if (copies.length)
|
|
2331
2345
|
lines.push("", N.actualize.copies, ...copies.map((c) => locale_default.fill(N.actualize.copy, c)));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
locale_default
|
|
3
|
-
} from "./harness-
|
|
3
|
+
} from "./harness-tdg4dnq3.js";
|
|
4
4
|
import {
|
|
5
5
|
invariant2,
|
|
6
6
|
codeOnly2,
|
|
7
7
|
envNamesInCode2
|
|
8
|
-
} from "./harness-
|
|
8
|
+
} from "./harness-bpyj4s91.js";
|
|
9
9
|
|
|
10
10
|
// skills/spec/scripts/architecture.ts
|
|
11
11
|
import { readdirSync, readFileSync } from "node:fs";
|
|
@@ -64,6 +64,10 @@ var en_default = {
|
|
|
64
64
|
epic: "## Epic #{epic} «{title}»",
|
|
65
65
|
openTasks: "## The epic's open tasks — its sub-issues, not a list in the epic's description",
|
|
66
66
|
openTask: "### #{number} «{title}»",
|
|
67
|
+
backlog: "{status} — a repeat finding of the same kind (the same helper, module or standard) goes here as an item in «What to do» (and a scenario, if needed), not into a new task.",
|
|
68
|
+
inWork: "{work} — the task is already going: a repeat finding of the same kind isn't appended here but filed as a new task.",
|
|
69
|
+
taskStatus: "Status «{status}»",
|
|
70
|
+
taskPr: "PR #{pr} is open",
|
|
67
71
|
noBody: "(no description)",
|
|
68
72
|
copies: "## Copies of links in text — remove them: GitHub keeps the links (sub-issue, blocked by)",
|
|
69
73
|
copy: "- #{number}: «{line}»",
|
|
@@ -71,8 +75,8 @@ var en_default = {
|
|
|
71
75
|
commentAnswer: "- #{number}, question {k}: «{text}» — {url}",
|
|
72
76
|
checks: "## Checks by meaning — yours (canon — reference.md of the github skill, «Epic refresh on task close»); the mechanics — blocked by on done tasks, the questions label without open questions, milestones without open tasks — the script did",
|
|
73
77
|
scope: "You change only GitHub tasks — description, «{questions}», links, status; the github skill script — `{script}`: not done — `task drop <N>`, a new one — `task new … --branch-type <type> --epic {epic}` (it sets the estimate itself); an answer at a question — `task answer <N> <k> \"<answer>\"`. Don't touch code, branches or git; don't close #{number}, epic #{epic} or their milestones — task close does that after you.",
|
|
74
|
-
comment: "The result — always, as the last step: a comment on epic #{epic} (`gh issue comment {epic} --repo {repo}`), one line «{report}» — after the colon what you fixed, nothing to fix — «the block is current». By it `task close {number}` sees the refresh is over: without it the close refuses.",
|
|
75
|
-
answer: "Answer — fixes one per line with task numbers and questions to the owner, if any; otherwise «The block is current».",
|
|
78
|
+
comment: "The result — always, as the last step: a comment on epic #{epic} (`gh issue comment {epic} --repo {repo}`), one line «{report}» — after the colon what you fixed, a repeat finding — with where it went: «appended to #N» or «filed #M»; nothing to fix — «the block is current». By it `task close {number}` sees the refresh is over: without it the close refuses.",
|
|
79
|
+
answer: "Answer — fixes one per line with task numbers (a repeat finding — «appended to #N» or «filed #M») and questions to the owner, if any; otherwise «The block is current».",
|
|
76
80
|
linkWords: "blocked by|blocks|after|depends on|waits for",
|
|
77
81
|
report: "Refresh after #{number}, result: {text}",
|
|
78
82
|
mechanics: "Refresh after #{number}, script: {fixes}.",
|
|
@@ -92,6 +96,7 @@ var en_default = {
|
|
|
92
96
|
subagents: "- writing subagents — launch as needed, no limit: each gets its own worktree and «{task} #{number}» at the start of its prompt (`est` links its work by it); bring their commits into your branch, remove their worktrees and branches yourself after the merge — what didn't go, name in the answer;",
|
|
93
97
|
live: "- while any of your subagents works, don't answer: wait for their answers — a background subagent wakes you with its answer; an answer with live subagents looks like a failure, and the dispatcher would give the task to a second executor next to you. Can't wait — answer `waits for subagents: <which and what they do>`: it isn't `failed`, there will be no second executor;",
|
|
94
98
|
refresh: '- the epic refresh — the subagent `task actualize` prints, without a worktree of its own (in yours) and not in the background (`run_in_background: false`): its answer — in the same turn, then `{script} task close {number}`, and only then the answer to the dispatcher: once you answer, a worktree without changes is removed, and a background subagent is left without a directory; a task without a PR (a data edit, a reconciliation, research) — first close the issue as completed with a result comment: `gh issue close {number} --repo {repo} --reason completed --comment "<result>"`;',
|
|
99
|
+
repeats: "- before the PR — repeats in the files the task changes anyway: fix them in the same PR, don't file a task for them; a repeat outside the task's files is the epic refresh's;",
|
|
95
100
|
found: "- work found outside the task — `{script} task new …` (work of the task's epic — with `--epic`: the dispatcher picks it up) — name it in the answer: whether to queue it is the owner's call;",
|
|
96
101
|
question: "- a question without a clear recommendation — into «{questions}» of the task; don't start the code that depends on it and answer the dispatcher: the owner's answer comes back here.",
|
|
97
102
|
answer: "The answer to the dispatcher — one line: `merged #<PR>` · `question: <the question, options, recommendation>` · `waits for subagents: <which and what they do>` · `failed: <what stopped and where>`.",
|
|
@@ -522,6 +527,10 @@ var ru_default = {
|
|
|
522
527
|
epic: "## Эпик #{epic} «{title}»",
|
|
523
528
|
openTasks: "## Открытые задачи эпика — его подзадачи (sub-issues), а не список в описании эпика",
|
|
524
529
|
openTask: "### #{number} «{title}»",
|
|
530
|
+
backlog: "{status} — находку-повтор того же рода (тот же помощник, модуль или стандарт) дописать сюда пунктом в «Что сделать» (и сценарий, если нужен), новую задачу не заводить.",
|
|
531
|
+
inWork: "{work} — задача уже идёт: находку-повтор того же рода сюда не дописывать, а завести новой задачей.",
|
|
532
|
+
taskStatus: "Статус «{status}»",
|
|
533
|
+
taskPr: "PR #{pr} открыт",
|
|
525
534
|
noBody: "(описания нет)",
|
|
526
535
|
copies: "## Копии связей в тексте — убрать: связи держит GitHub (sub-issue, blocked by)",
|
|
527
536
|
copy: "- #{number}: «{line}»",
|
|
@@ -529,8 +538,8 @@ var ru_default = {
|
|
|
529
538
|
commentAnswer: "- #{number}, вопрос {k}: «{text}» — {url}",
|
|
530
539
|
checks: "## Проверки по смыслу — твои (канон — reference.md скилла github, «Epic refresh on task close»); механику — blocked by на выполненных задачах, метку вопросов без открытых вопросов, milestone без открытых задач — сделал скрипт",
|
|
531
540
|
scope: 'Меняешь только задачи GitHub — описание, «{questions}», связи, статус; скрипт скилла github — `{script}`: без выполнения — `task drop <N>`, новая — `task new … --branch-type <тип> --epic {epic}` (оценку ставит сама); ответ у вопроса — `task answer <N> <k> "<ответ>"`. Код, ветки и git не трогать; #{number}, эпик #{epic} и их milestone не закрывать — это сделает task close после тебя.',
|
|
532
|
-
comment: "Итог — всегда, последним шагом: комментарий к эпику #{epic} (`gh issue comment {epic} --repo {repo}`) одной строкой «{report}» — после двоеточия что поправил,
|
|
533
|
-
answer: "Ответ — правки по строке с номерами задач и вопросы владельцу, если есть; иначе «Блок актуален».",
|
|
541
|
+
comment: "Итог — всегда, последним шагом: комментарий к эпику #{epic} (`gh issue comment {epic} --repo {repo}`) одной строкой «{report}» — после двоеточия что поправил, находку-повтор — с тем, куда она ушла: «дописано в #N» или «заведена #M»; править нечего — «блок актуален». По нему `task close {number}` видит, что актуализация закончена: без него закрытие откажет.",
|
|
542
|
+
answer: "Ответ — правки по строке с номерами задач (находка-повтор — «дописано в #N» или «заведена #M») и вопросы владельцу, если есть; иначе «Блок актуален».",
|
|
534
543
|
linkWords: "после|зависит от|заблокирована|заблокирован|заблокировано|блокирует|ждёт|ждет",
|
|
535
544
|
report: "Актуализация после #{number}, итог: {text}",
|
|
536
545
|
mechanics: "Актуализация после #{number}, скрипт: {fixes}.",
|
|
@@ -550,6 +559,7 @@ var ru_default = {
|
|
|
550
559
|
subagents: "- пишущих субагентов запускай по необходимости, без лимита: каждому — свой worktree и «{task} #{number}» в начале промпта (по нему `est` привяжет его работу); их коммиты перенеси в свою ветку, их worktree и ветки после мержа убери сам — что не убралось, назови в ответе;",
|
|
551
560
|
live: "- пока работает хоть один твой субагент, ответ не отдавай: жди их ответов — фоновый субагент будит тебя своим ответом; ответ при живых субагентах выглядит провалом, и диспетчер отдал бы задачу второму исполнителю рядом с тобой. Ждать нельзя — ответь `ждёт субагентов: <какие и что делают>`: это не «сломалось», второго исполнителя не будет;",
|
|
552
561
|
refresh: '- актуализация эпика — субагент, которого печатает `task actualize`, без своего worktree (в твоём) и не в фоне (`run_in_background: false`): его ответ — в том же ходе, затем `{script} task close {number}`, и только потом ответ диспетчеру: стоит тебе ответить, worktree без изменений убирают, и фоновый субагент остаётся без каталога; у задачи без PR (правка данных, сверка, исследование) — сначала закрой issue как выполненную с итоговым комментарием: `gh issue close {number} --repo {repo} --reason completed --comment "<итог>"`;',
|
|
562
|
+
repeats: "- перед PR — повторы в файлах, которые задача и так правит: исправь в том же PR, новую задачу на них не заводи; повтор вне файлов задачи — дело актуализации эпика;",
|
|
553
563
|
found: "- найденная работа вне задачи — `{script} task new …` (работа эпика задачи — с `--epic`: диспетчер её подхватит) — назови её в ответе: ставить ли её в очередь, решает владелец;",
|
|
554
564
|
question: "- вопрос без ясной рекомендации — в «{questions}» задачи; код, который от него зависит, не начинай и ответь диспетчеру: ответ владельца придёт сюда же.",
|
|
555
565
|
answer: "Ответ диспетчеру — одной строкой: `влито #<PR>` · `вопрос: <вопрос, варианты, рекомендация>` · `ждёт субагентов: <какие и что делают>` · `сломалось: <что и где остановилось>`.",
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
envNamesInCode2,
|
|
24
24
|
dotenvNames2,
|
|
25
25
|
envVars2
|
|
26
|
-
} from "./harness-
|
|
27
|
-
import"./harness-
|
|
26
|
+
} from "./harness-bpyj4s91.js";
|
|
27
|
+
import"./harness-tdg4dnq3.js";
|
|
28
28
|
export {
|
|
29
29
|
ENV_SERVICE2 as ENV_SERVICE,
|
|
30
30
|
codeFiles2 as codeFiles,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miroshnik/elves",
|
|
3
|
-
"version": "2026.1019.
|
|
3
|
+
"version": "2026.1019.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Elves — a development flow with AI agents: shared rules and skills for any agent; install — bunx @miroshnik/elves install -g; the installer and skill scripts — TypeScript under Bun, tests — bun test in tests/",
|
|
6
6
|
"repository": {
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"eslint-plugin-boundaries": "^7.2.0",
|
|
72
72
|
"knip": "^6.41.0"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "006208c32b61d184886a5b77dbdffa39bba751f2"
|
|
75
75
|
}
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"epic": "## Epic #{epic} «{title}»",
|
|
61
61
|
"openTasks": "## The epic's open tasks — its sub-issues, not a list in the epic's description",
|
|
62
62
|
"openTask": "### #{number} «{title}»",
|
|
63
|
+
"backlog": "{status} — a repeat finding of the same kind (the same helper, module or standard) goes here as an item in «What to do» (and a scenario, if needed), not into a new task.",
|
|
64
|
+
"inWork": "{work} — the task is already going: a repeat finding of the same kind isn't appended here but filed as a new task.",
|
|
65
|
+
"taskStatus": "Status «{status}»",
|
|
66
|
+
"taskPr": "PR #{pr} is open",
|
|
63
67
|
"noBody": "(no description)",
|
|
64
68
|
"copies": "## Copies of links in text — remove them: GitHub keeps the links (sub-issue, blocked by)",
|
|
65
69
|
"copy": "- #{number}: «{line}»",
|
|
@@ -67,8 +71,8 @@
|
|
|
67
71
|
"commentAnswer": "- #{number}, question {k}: «{text}» — {url}",
|
|
68
72
|
"checks": "## Checks by meaning — yours (canon — reference.md of the github skill, «Epic refresh on task close»); the mechanics — blocked by on done tasks, the questions label without open questions, milestones without open tasks — the script did",
|
|
69
73
|
"scope": "You change only GitHub tasks — description, «{questions}», links, status; the github skill script — `{script}`: not done — `task drop <N>`, a new one — `task new … --branch-type <type> --epic {epic}` (it sets the estimate itself); an answer at a question — `task answer <N> <k> \"<answer>\"`. Don't touch code, branches or git; don't close #{number}, epic #{epic} or their milestones — task close does that after you.",
|
|
70
|
-
"comment": "The result — always, as the last step: a comment on epic #{epic} (`gh issue comment {epic} --repo {repo}`), one line «{report}» — after the colon what you fixed, nothing to fix — «the block is current». By it `task close {number}` sees the refresh is over: without it the close refuses.",
|
|
71
|
-
"answer": "Answer — fixes one per line with task numbers and questions to the owner, if any; otherwise «The block is current».",
|
|
74
|
+
"comment": "The result — always, as the last step: a comment on epic #{epic} (`gh issue comment {epic} --repo {repo}`), one line «{report}» — after the colon what you fixed, a repeat finding — with where it went: «appended to #N» or «filed #M»; nothing to fix — «the block is current». By it `task close {number}` sees the refresh is over: without it the close refuses.",
|
|
75
|
+
"answer": "Answer — fixes one per line with task numbers (a repeat finding — «appended to #N» or «filed #M») and questions to the owner, if any; otherwise «The block is current».",
|
|
72
76
|
"linkWords": "blocked by|blocks|after|depends on|waits for",
|
|
73
77
|
"report": "Refresh after #{number}, result: {text}",
|
|
74
78
|
"mechanics": "Refresh after #{number}, script: {fixes}.",
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
"subagents": "- writing subagents — launch as needed, no limit: each gets its own worktree and «{task} #{number}» at the start of its prompt (`est` links its work by it); bring their commits into your branch, remove their worktrees and branches yourself after the merge — what didn't go, name in the answer;",
|
|
89
93
|
"live": "- while any of your subagents works, don't answer: wait for their answers — a background subagent wakes you with its answer; an answer with live subagents looks like a failure, and the dispatcher would give the task to a second executor next to you. Can't wait — answer `waits for subagents: <which and what they do>`: it isn't `failed`, there will be no second executor;",
|
|
90
94
|
"refresh": "- the epic refresh — the subagent `task actualize` prints, without a worktree of its own (in yours) and not in the background (`run_in_background: false`): its answer — in the same turn, then `{script} task close {number}`, and only then the answer to the dispatcher: once you answer, a worktree without changes is removed, and a background subagent is left without a directory; a task without a PR (a data edit, a reconciliation, research) — first close the issue as completed with a result comment: `gh issue close {number} --repo {repo} --reason completed --comment \"<result>\"`;",
|
|
95
|
+
"repeats": "- before the PR — repeats in the files the task changes anyway: fix them in the same PR, don't file a task for them; a repeat outside the task's files is the epic refresh's;",
|
|
91
96
|
"found": "- work found outside the task — `{script} task new …` (work of the task's epic — with `--epic`: the dispatcher picks it up) — name it in the answer: whether to queue it is the owner's call;",
|
|
92
97
|
"question": "- a question without a clear recommendation — into «{questions}» of the task; don't start the code that depends on it and answer the dispatcher: the owner's answer comes back here.",
|
|
93
98
|
"answer": "The answer to the dispatcher — one line: `merged #<PR>` · `question: <the question, options, recommendation>` · `waits for subagents: <which and what they do>` · `failed: <what stopped and where>`.",
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"epic": "## Эпик #{epic} «{title}»",
|
|
61
61
|
"openTasks": "## Открытые задачи эпика — его подзадачи (sub-issues), а не список в описании эпика",
|
|
62
62
|
"openTask": "### #{number} «{title}»",
|
|
63
|
+
"backlog": "{status} — находку-повтор того же рода (тот же помощник, модуль или стандарт) дописать сюда пунктом в «Что сделать» (и сценарий, если нужен), новую задачу не заводить.",
|
|
64
|
+
"inWork": "{work} — задача уже идёт: находку-повтор того же рода сюда не дописывать, а завести новой задачей.",
|
|
65
|
+
"taskStatus": "Статус «{status}»",
|
|
66
|
+
"taskPr": "PR #{pr} открыт",
|
|
63
67
|
"noBody": "(описания нет)",
|
|
64
68
|
"copies": "## Копии связей в тексте — убрать: связи держит GitHub (sub-issue, blocked by)",
|
|
65
69
|
"copy": "- #{number}: «{line}»",
|
|
@@ -67,8 +71,8 @@
|
|
|
67
71
|
"commentAnswer": "- #{number}, вопрос {k}: «{text}» — {url}",
|
|
68
72
|
"checks": "## Проверки по смыслу — твои (канон — reference.md скилла github, «Epic refresh on task close»); механику — blocked by на выполненных задачах, метку вопросов без открытых вопросов, milestone без открытых задач — сделал скрипт",
|
|
69
73
|
"scope": "Меняешь только задачи GitHub — описание, «{questions}», связи, статус; скрипт скилла github — `{script}`: без выполнения — `task drop <N>`, новая — `task new … --branch-type <тип> --epic {epic}` (оценку ставит сама); ответ у вопроса — `task answer <N> <k> \"<ответ>\"`. Код, ветки и git не трогать; #{number}, эпик #{epic} и их milestone не закрывать — это сделает task close после тебя.",
|
|
70
|
-
"comment": "Итог — всегда, последним шагом: комментарий к эпику #{epic} (`gh issue comment {epic} --repo {repo}`) одной строкой «{report}» — после двоеточия что поправил,
|
|
71
|
-
"answer": "Ответ — правки по строке с номерами задач и вопросы владельцу, если есть; иначе «Блок актуален».",
|
|
74
|
+
"comment": "Итог — всегда, последним шагом: комментарий к эпику #{epic} (`gh issue comment {epic} --repo {repo}`) одной строкой «{report}» — после двоеточия что поправил, находку-повтор — с тем, куда она ушла: «дописано в #N» или «заведена #M»; править нечего — «блок актуален». По нему `task close {number}` видит, что актуализация закончена: без него закрытие откажет.",
|
|
75
|
+
"answer": "Ответ — правки по строке с номерами задач (находка-повтор — «дописано в #N» или «заведена #M») и вопросы владельцу, если есть; иначе «Блок актуален».",
|
|
72
76
|
"linkWords": "после|зависит от|заблокирована|заблокирован|заблокировано|блокирует|ждёт|ждет",
|
|
73
77
|
"report": "Актуализация после #{number}, итог: {text}",
|
|
74
78
|
"mechanics": "Актуализация после #{number}, скрипт: {fixes}.",
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
"subagents": "- пишущих субагентов запускай по необходимости, без лимита: каждому — свой worktree и «{task} #{number}» в начале промпта (по нему `est` привяжет его работу); их коммиты перенеси в свою ветку, их worktree и ветки после мержа убери сам — что не убралось, назови в ответе;",
|
|
89
93
|
"live": "- пока работает хоть один твой субагент, ответ не отдавай: жди их ответов — фоновый субагент будит тебя своим ответом; ответ при живых субагентах выглядит провалом, и диспетчер отдал бы задачу второму исполнителю рядом с тобой. Ждать нельзя — ответь `ждёт субагентов: <какие и что делают>`: это не «сломалось», второго исполнителя не будет;",
|
|
90
94
|
"refresh": "- актуализация эпика — субагент, которого печатает `task actualize`, без своего worktree (в твоём) и не в фоне (`run_in_background: false`): его ответ — в том же ходе, затем `{script} task close {number}`, и только потом ответ диспетчеру: стоит тебе ответить, worktree без изменений убирают, и фоновый субагент остаётся без каталога; у задачи без PR (правка данных, сверка, исследование) — сначала закрой issue как выполненную с итоговым комментарием: `gh issue close {number} --repo {repo} --reason completed --comment \"<итог>\"`;",
|
|
95
|
+
"repeats": "- перед PR — повторы в файлах, которые задача и так правит: исправь в том же PR, новую задачу на них не заводи; повтор вне файлов задачи — дело актуализации эпика;",
|
|
91
96
|
"found": "- найденная работа вне задачи — `{script} task new …` (работа эпика задачи — с `--epic`: диспетчер её подхватит) — назови её в ответе: ставить ли её в очередь, решает владелец;",
|
|
92
97
|
"question": "- вопрос без ясной рекомендации — в «{questions}» задачи; код, который от него зависит, не начинай и ответь диспетчеру: ответ владельца придёт сюда же.",
|
|
93
98
|
"answer": "Ответ диспетчеру — одной строкой: `влито #<PR>` · `вопрос: <вопрос, варианты, рекомендация>` · `ждёт субагентов: <какие и что делают>` · `сломалось: <что и где остановилось>`.",
|
|
@@ -246,12 +246,20 @@ line `○ refresh not needed`, no subagent. Five checks:
|
|
|
246
246
|
of subtasks in the epic, an order, "⛔" or "after #N" marks — is removed.
|
|
247
247
|
5. **Repetition is a standard.** A decision that, in what shipped, repeats an
|
|
248
248
|
existing one in two or more places — a `Standard · …` issue
|
|
249
|
-
(a rule in `tests/standards/` and a refactoring)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
the
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
(a rule in `tests/standards/` and a refactoring); a repeat in the files a
|
|
250
|
+
task changes anyway is fixed in its PR by its executor, without a task.
|
|
251
|
+
First — an open task of the epic of the same kind (the same helper, module
|
|
252
|
+
or standard) in Backlog: the finding is an item in its «What to do» (and a
|
|
253
|
+
scenario, if needed), not a new task; one In progress or with an open PR is
|
|
254
|
+
already going — a new task (the assignment names each one's status). Then
|
|
255
|
+
a search among the open `Standard · …`: one exists — don't file another; an
|
|
256
|
+
extension of a neighboring one — a comment in it. The result names where
|
|
257
|
+
the finding went: «appended to #N» or «filed #M». A standard's priority —
|
|
258
|
+
by the cost of the mistake it prevents; standards of one area — as an epic:
|
|
259
|
+
otherwise they are born faster than they are worked off (#636: seven tasks
|
|
260
|
+
in a day on one helper's copies, each refresh finding the next). Something
|
|
261
|
+
deferred as "not in this PR" — an issue or dropped explicitly, not
|
|
262
|
+
forgotten.
|
|
255
263
|
|
|
256
264
|
What was fixed — one comment on the epic, the history of decisions in one
|
|
257
265
|
place; it is the subagent's last step, always: nothing to fix — «the block is
|
|
@@ -2415,7 +2415,7 @@ function cloudBrief(io: Io, slug: string, issue: Issue): void {
|
|
|
2415
2415
|
state: N.comments.state,
|
|
2416
2416
|
questions: N.sections.questions,
|
|
2417
2417
|
};
|
|
2418
|
-
for (const line of [N.queue.cloud, N.queue.cloudDoc, N.queue.cloudClose, N.queue.cloudDispatcher, N.queue.cloudQuestion]) io.out(locale.fill(line, vars));
|
|
2418
|
+
for (const line of [N.queue.cloud, N.queue.cloudDoc, N.queue.repeats, N.queue.cloudClose, N.queue.cloudDispatcher, N.queue.cloudQuestion]) io.out(locale.fill(line, vars));
|
|
2419
2419
|
io.out(
|
|
2420
2420
|
`Next: the lines above — the cloud session's first prompt as is, the session started from the project folder (the cloud starts from main); ` +
|
|
2421
2421
|
`then as a background command: bash ${waitCi()} issue ${number} — a cloud session can't message a local one (docs/cloud-sessions.md), the task's closing is the signal: ` +
|
|
@@ -2436,6 +2436,17 @@ const SESSION_ARCHIVE =
|
|
|
2436
2436
|
"an executor session whose task closed — the dispatcher archives it in an auto project where the agent has the tool, " +
|
|
2437
2437
|
"not one still working in the background or awaiting the owner's answer; outside auto the owner closes it";
|
|
2438
2438
|
|
|
2439
|
+
/**
|
|
2440
|
+
* A task's open PR: linked to it by GitHub, or one of the repository's recent ones by the task branch or `Closes #N` in
|
|
2441
|
+
* its body. The queue's «=» (the executor waits for its CI) and the epic refresh's «already going» (#636).
|
|
2442
|
+
*/
|
|
2443
|
+
function openPrOf(io: Io, slug: string): (i: Issue) => number | undefined {
|
|
2444
|
+
const [owner, name] = slug.split("/");
|
|
2445
|
+
const recent = (graphql(io, Q.RecentPrs, { owner, name })?.repository?.pullRequests?.nodes ?? []).filter((p: Any) => p.state === "OPEN");
|
|
2446
|
+
return (i) =>
|
|
2447
|
+
i.prs.find((p) => p.state === "OPEN")?.number ?? recent.find((p: Any) => isTaskBranch(p.headRefName ?? "", i.number) || closingInBody(p.body ?? "").includes(i.number))?.number;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2439
2450
|
/**
|
|
2440
2451
|
* The dispatcher's queue: its ready tasks go to background executors (subagents), K at a time; the queue is re-read
|
|
2441
2452
|
* after each answer, so tasks that appeared meanwhile are picked up. A session with its own task dispatches too (#546:
|
|
@@ -2466,10 +2477,7 @@ export function cmdTaskQueue(io: Io, slug: string, o: QueueFilter): number {
|
|
|
2466
2477
|
if (found.length >= 100) io.out("○ the search gave 100 open tasks — the queue sees only them: narrow it with --epic");
|
|
2467
2478
|
numbers = found.filter((x) => x?.state === "OPEN").map((x) => x.number);
|
|
2468
2479
|
}
|
|
2469
|
-
const
|
|
2470
|
-
const recent = (graphql(io, Q.RecentPrs, { owner, name })?.repository?.pullRequests?.nodes ?? []).filter((p: Any) => p.state === "OPEN");
|
|
2471
|
-
const openPr = (i: Issue) =>
|
|
2472
|
-
i.prs.find((p) => p.state === "OPEN")?.number ?? recent.find((p: Any) => isTaskBranch(p.headRefName ?? "", i.number) || closingInBody(p.body ?? "").includes(i.number))?.number;
|
|
2480
|
+
const openPr = openPrOf(io, slug);
|
|
2473
2481
|
const handed = handedOut(io, slug);
|
|
2474
2482
|
/** Issue states the queue has read; a hand-out's task outside the queue is read when its worktree is up for removal. */
|
|
2475
2483
|
const states = new Map<number, string>();
|
|
@@ -2930,8 +2938,8 @@ export function cmdTaskAssignment(io: Io, slug: string, number: number, o: { clo
|
|
|
2930
2938
|
const resume = restart.length ? [N.queue.resume] : [];
|
|
2931
2939
|
// the epic refresh in the same turn (`refresh`, #628) — the subagent's: a session runs it by the canon, with the deploy's wait
|
|
2932
2940
|
const lines = o.session
|
|
2933
|
-
? [N.queue.sessionLead, ...resume, N.queue.sessionIs, N.queue.sessionName, N.queue.sessionWait, N.queue.sessionFound, N.queue.sessionQuestion, N.queue.sessionEnd]
|
|
2934
|
-
: [N.queue.lead, ...resume, N.queue.subagent, N.queue.session, N.queue.wait, N.queue.git, N.queue.subagents, N.queue.live, N.queue.refresh, N.queue.found, N.queue.question, N.queue.answer];
|
|
2941
|
+
? [N.queue.sessionLead, ...resume, N.queue.sessionIs, N.queue.sessionName, N.queue.sessionWait, N.queue.repeats, N.queue.sessionFound, N.queue.sessionQuestion, N.queue.sessionEnd]
|
|
2942
|
+
: [N.queue.lead, ...resume, N.queue.subagent, N.queue.session, N.queue.wait, N.queue.git, N.queue.subagents, N.queue.live, N.queue.refresh, N.queue.repeats, N.queue.found, N.queue.question, N.queue.answer];
|
|
2935
2943
|
for (const line of lines) io.out(locale.fill(line, vars));
|
|
2936
2944
|
if (o.session) {
|
|
2937
2945
|
io.out(
|
|
@@ -3124,8 +3132,9 @@ const textBlock = (body: string) => (body ? `~~~~markdown\n${body}\n~~~~` : N.ac
|
|
|
3124
3132
|
* its open tasks and the PR files run to tens of KB, and the session at peak context would copy them into the subagent
|
|
3125
3133
|
* call as output tokens. In it — the task and its PR with its files, the epic and its open tasks with their texts, the
|
|
3126
3134
|
* copies of links and the answers in comments the script found, the checks by meaning quoted from reference.md as is,
|
|
3127
|
-
* what may be changed and the answer form;
|
|
3128
|
-
*
|
|
3135
|
+
* what may be changed and the answer form; under each open task — where a repeat finding of check 5 goes, by its Status
|
|
3136
|
+
* and open PR (#636); in the project language (the `actualize` keys). No epic, or no open tasks in it besides this one
|
|
3137
|
+
* — the checks by meaning have nothing to go through: a "not needed" line, no subagent.
|
|
3129
3138
|
*/
|
|
3130
3139
|
export function cmdTaskActualize(io: Io, slug: string, number: number, o: { assignment: boolean } = { assignment: false }): number {
|
|
3131
3140
|
const issue = loadIssue(io, slug, number);
|
|
@@ -3137,6 +3146,8 @@ export function cmdTaskActualize(io: Io, slug: string, number: number, o: { assi
|
|
|
3137
3146
|
const open = epic.subIssues.filter((x) => x.state === "OPEN" && x.number !== number).map((x) => loadTaskText(io, slug, x.number));
|
|
3138
3147
|
if (!o.assignment) refreshMechanics(io, slug, { number, merged: prs.length > 0, epic, open });
|
|
3139
3148
|
if (!open.length) return io.out(`○ refresh not needed: epic #${epic.number} has no open tasks besides #${number}. ${later}`), 0;
|
|
3149
|
+
// the board with its Status (and its language) — the assignment names where a repeat finding goes (#636)
|
|
3150
|
+
const ctx = o.assignment ? taskContext(io, slug) : null;
|
|
3140
3151
|
const vars = { task: N.subagentPrompt.task, epicWord: N.subagentPrompt.epic, number, epic: epic.number, repo: slug };
|
|
3141
3152
|
const lead = locale.fill(N.actualize.lead, vars);
|
|
3142
3153
|
if (!o.assignment) {
|
|
@@ -3155,7 +3166,17 @@ export function cmdTaskActualize(io: Io, slug: string, number: number, o: { assi
|
|
|
3155
3166
|
if (files.length) lines.push(locale.fill(N.actualize.files, { pr: p.number }), ...files.map(fileLine));
|
|
3156
3167
|
}
|
|
3157
3168
|
lines.push("", locale.fill(N.actualize.epic, { ...vars, title: epic.title }), textBlock(epic.body), "", N.actualize.openTasks);
|
|
3158
|
-
|
|
3169
|
+
const openPr = openPrOf(io, slug);
|
|
3170
|
+
/** Where a repeat finding of check 5 goes (#636): a task in Backlog takes it as an item, one already going doesn't. */
|
|
3171
|
+
const target = (n: number): string[] => {
|
|
3172
|
+
const i = loadIssue(io, slug, n);
|
|
3173
|
+
const status = itemIn(ctx!, i)?.status ?? null;
|
|
3174
|
+
const pr = openPr(i);
|
|
3175
|
+
const work = [status === N.status.inProgress ? locale.fill(N.actualize.taskStatus, { status }) : "", pr === undefined ? "" : locale.fill(N.actualize.taskPr, { pr })].filter(Boolean);
|
|
3176
|
+
if (work.length) return [locale.fill(N.actualize.inWork, { work: work.join(", ") })];
|
|
3177
|
+
return status === N.status.backlog ? [locale.fill(N.actualize.backlog, { status: locale.fill(N.actualize.taskStatus, { status }) })] : [];
|
|
3178
|
+
};
|
|
3179
|
+
for (const t of open) lines.push("", locale.fill(N.actualize.openTask, { number: t.number, title: t.title }), ...target(t.number), textBlock(t.body));
|
|
3159
3180
|
const copies = linkCopies(epic, open);
|
|
3160
3181
|
if (copies.length) lines.push("", N.actualize.copies, ...copies.map((c) => locale.fill(N.actualize.copy, c)));
|
|
3161
3182
|
const answers = commentAnswers([epic, ...open]);
|
|
@@ -73,6 +73,14 @@ export interface Names {
|
|
|
73
73
|
epic: string;
|
|
74
74
|
openTasks: string;
|
|
75
75
|
openTask: string;
|
|
76
|
+
/**
|
|
77
|
+
* Where a repeat finding of check 5 goes, under an open task's heading (#636): a task in Backlog takes it as an item
|
|
78
|
+
* (`backlog`), one already going — In progress or with an open PR — doesn't (`inWork`); `taskStatus`, `taskPr` — why.
|
|
79
|
+
*/
|
|
80
|
+
backlog: string;
|
|
81
|
+
inWork: string;
|
|
82
|
+
taskStatus: string;
|
|
83
|
+
taskPr: string;
|
|
76
84
|
noBody: string;
|
|
77
85
|
copies: string;
|
|
78
86
|
copy: string;
|
|
@@ -104,6 +112,8 @@ export interface Names {
|
|
|
104
112
|
live: string;
|
|
105
113
|
/** The epic refresh — in the same turn, before `task close` and the answer; a task without a PR closed first (#628). */
|
|
106
114
|
refresh: string;
|
|
115
|
+
/** Repeats in the files the task changes anyway — fixed in its PR, not a new task (#636); every kind of executor. */
|
|
116
|
+
repeats: string;
|
|
107
117
|
found: string;
|
|
108
118
|
question: string;
|
|
109
119
|
answer: string;
|