@irythill/orq 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -38
- package/README.pt-BR.md +60 -38
- package/dist/agents.js +80 -6
- package/dist/ci.js +6 -6
- package/dist/cli.js +3 -1
- package/dist/commands/agent.js +146 -20
- package/dist/commands/board.js +90 -20
- package/dist/commands/claim.js +2 -0
- package/dist/commands/completion.js +1 -1
- package/dist/commands/conductor.js +104 -0
- package/dist/commands/gc.js +57 -21
- package/dist/commands/handoff.js +1 -0
- package/dist/commands/quota.js +1 -1
- package/dist/commands/rounds.js +45 -18
- package/dist/commands/wait.js +5 -1
- package/dist/config.js +5 -1
- package/dist/install.js +5 -0
- package/dist/issue.js +47 -0
- package/dist/notice.js +33 -5
- package/dist/pick.js +105 -17
- package/dist/process.js +7 -0
- package/dist/quota.js +85 -19
- package/dist/stats.js +2 -1
- package/package.json +3 -2
- package/skills/issue/SKILL.md +41 -41
- package/templates/conductor.instructions.md +31 -0
- package/templates/workspace.CLAUDE.md +6 -5
package/README.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center"><img src="docs/img/orq-logo.svg" alt="orq — an orc in a tailcoat raising a conductor's baton" width="304"></p>
|
|
4
4
|
|
|
5
|
-
# orq — orchestrating coding agents
|
|
5
|
+
# orq — orchestrating coding agents
|
|
6
6
|
|
|
7
7
|
You describe the work in a Plane issue. A **conductor** (Claude, ideally Opus) reads it, picks the right agent for
|
|
8
|
-
each step, dispatches the **implementer**, the **tests** and a **cross-vendor review** in
|
|
9
|
-
|
|
8
|
+
each step, dispatches the **implementer**, the **tests** and a **cross-vendor review** (each in its own tab or window
|
|
9
|
+
in [herdr](https://herdr.dev) or tmux, or in the background), and hands the issue back *In Review* with a summary in
|
|
10
|
+
Plane. Merge, PR and deploy stay with you.
|
|
10
11
|
|
|
11
12
|
What orq does **not** decide for you: which agents you have, how much each may spend and how you like to work. All of
|
|
12
13
|
that lives in your config.
|
|
@@ -19,17 +20,18 @@ that lives in your config.
|
|
|
19
20
|
## How it works
|
|
20
21
|
|
|
21
22
|
```
|
|
22
|
-
You ── "/issue DEMO-123" ──► conductor (Claude in
|
|
23
|
-
│ orq
|
|
24
|
-
├─► implementer
|
|
25
|
-
├─► tests
|
|
26
|
-
│ orq
|
|
27
|
-
├─► reviewer
|
|
23
|
+
You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
|
|
24
|
+
│ orq pick impl ──► which agent/model implements (config + history + quota)
|
|
25
|
+
├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
|
|
26
|
+
├─► tests orq verify only the diff's tests · queue of one suite at a time
|
|
27
|
+
│ orq pick review ─► a reviewer from ANOTHER vendor
|
|
28
|
+
├─► reviewer orq agent -r read only · diff + criteria + test results attached
|
|
28
29
|
└─► Plane comment, state, labels
|
|
29
|
-
◄── "[HERDR-AGENT DONE] ..."
|
|
30
|
+
◄── "[HERDR-AGENT DONE] ..." typed into the conductor's herdr pane, or `orq wait` printing "[ORQ AGENT DONE] ..."
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
- **Every step in
|
|
33
|
+
- **Every step in its own tab or window.** In herdr or tmux you watch it live and the tab closes when it's done;
|
|
34
|
+
without them the step runs in the background (`orq board` shows it). Results stay in
|
|
33
35
|
`.orquestra/<ID>/` (`.out.md`, `.log`, `.done`).
|
|
34
36
|
- **Fixed order every round:** implementer ok → tests with no new failure → review. Only a `[blocker]` with
|
|
35
37
|
`file:line` and a concrete failure becomes a fix round; the rest goes into the Plane comment (configurable maximum).
|
|
@@ -37,7 +39,7 @@ You ── "/issue DEMO-123" ──► conductor (Claude in herdr)
|
|
|
37
39
|
one level up), fixes only the listed blockers with a test each, and the next review, by the same reviewer, checks
|
|
38
40
|
those blockers and the fix's own diff instead of starting over. The implementer ends with a `## Decisions` section, so
|
|
39
41
|
a deliberate choice doesn't come back as a blocker.
|
|
40
|
-
- **Nobody picks an agent by gut feeling.** `orq
|
|
42
|
+
- **Nobody picks an agent by gut feeling.** `orq pick` decides from data: what each agent delivered in the history,
|
|
41
43
|
how many tokens it spent, how long it took, the current quota, and a rotation so no agent gets all the work.
|
|
42
44
|
- **The machine stays up.** Each agent runs under its own memory cap; tests wait in a queue.
|
|
43
45
|
|
|
@@ -160,6 +162,9 @@ handoff_at = 85 # % of the window → orq handoff moves
|
|
|
160
162
|
max_per_account = 2 # an account already holding this many conductors is never a handoff target
|
|
161
163
|
reset_grace_min = 15 # every window over the trigger resetting within this many minutes skips the move
|
|
162
164
|
|
|
165
|
+
[git]
|
|
166
|
+
issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Branch/worktree name → issue ID, for `orq gc` and `orq ci` — default shown; regex, named group `issue` (else group 1)
|
|
167
|
+
|
|
163
168
|
[policy]
|
|
164
169
|
quota_week = 95 # ≥ 95% of the week → agent out
|
|
165
170
|
quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
|
|
@@ -188,13 +193,12 @@ orq pick fix --same .orquestra/<ID>/impl-<repo>.pick # a fix round: the implem
|
|
|
188
193
|
```
|
|
189
194
|
|
|
190
195
|
### Language and theme
|
|
191
|
-
orq speaks English and Portuguese: CLI messages, `
|
|
196
|
+
orq speaks English and Portuguese: CLI messages, `orq board`, notices and `install.sh`. It follows
|
|
192
197
|
`[ui] lang` in your config (`"auto"` = system locale, `"en"`, `"pt"`), or the `ORQ_LANG` environment variable.
|
|
193
198
|
Switch it any time:
|
|
194
199
|
- `orq-lang pt` / `orq-lang en` / `orq-lang toggle` (`orq-lang` alone shows the current one);
|
|
195
200
|
- `l` inside `orq board`;
|
|
196
|
-
-
|
|
197
|
-
`herdr-plugin/herdr-plugin.toml`) — e.g. `prefix+shift+l` — and `orq.board` to open the board as an overlay.
|
|
201
|
+
- in herdr, a key for it: see [With herdr](#with-herdr).
|
|
198
202
|
|
|
199
203
|
`orq board` has its own look, from the [SYNTH.DECK](https://github.com/irythill/synth-deck-portfolio)
|
|
200
204
|
portfolio: `[ui] theme = "gunmetal"` (default, dark graphite), `"chrome"` (light silver), `"lcd"` (phosphor green) or
|
|
@@ -205,32 +209,49 @@ Whatever is already running keeps its language. Conductors always talk to you in
|
|
|
205
209
|
|
|
206
210
|
## Daily use
|
|
207
211
|
|
|
212
|
+
Open the conductor in any terminal, in your workspace folder (the one `orq setup` prepared):
|
|
208
213
|
```bash
|
|
209
|
-
|
|
210
|
-
cd ~/Code/<workspace> && herdr-conductor # inside a herdr pane: opens the conductor (account and model optional)
|
|
214
|
+
cd ~/Code/<workspace> && orq conductor [account] [model] # any terminal: loads the conductor instructions, the workspace CLAUDE.md and the /issue skill
|
|
211
215
|
```
|
|
212
|
-
|
|
216
|
+
With several conductors, give each a fixed name (`export ORQ_CONDUCTOR_ID=<name>`) so `orq claim` can tell them apart.
|
|
217
|
+
Variants:
|
|
218
|
+
- **herdr:** run `herdr` (the server runs as a service), then the same `orq conductor` in a pane (`herdr-conductor` still works).
|
|
219
|
+
- **tmux:** run `orq conductor` in a pane; children open as tmux windows.
|
|
220
|
+
- **Neither:** children run in the background and the conductor waits with `orq wait`.
|
|
221
|
+
|
|
222
|
+
In the conductor: **`/issue DEMO-123`**. It answers in one line and moves on by itself at every completion signal
|
|
223
|
+
(`[HERDR-AGENT DONE]` in a herdr pane, otherwise the `[ORQ AGENT DONE]` line printed by `orq wait`).
|
|
213
224
|
You only come back when it asks for a decision or says it's done.
|
|
214
225
|
|
|
215
226
|
To see everything at once, keep **`orq board`** open in a tab or window (any terminal, any OS; `herdr-board` calls it):
|
|
216
227
|
test queue, running agents (model, memory against the cap, last action), conductors and their issues, memory, quota,
|
|
217
|
-
CI/CD per claimed issue, blocks and the 7-day scoreboard. `--once` prints it once, `--json` for scripts.
|
|
218
|
-
`ui.toast.delivery = "system"` in herdr's config, each finished step also pops up a native OS notification.
|
|
228
|
+
CI/CD per claimed issue, blocks and the 7-day scoreboard. `--once` prints it once, `--json` for scripts.
|
|
219
229
|
A failure (tests red, agent `status=fail`, CI red) is also **loud**: besides the line typed into the conductor's pane
|
|
220
|
-
(delivered by orq with retries and a log, `orq notice`), you get a
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`orq
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
(delivered by orq with retries and a log, `orq notice`), you get a `✗` title on the conductor's pane (herdr) and, on
|
|
231
|
+
Linux, a desktop notification (`[ui] desktop_notify = false` turns that one off; needs `notify-send`). Successes are
|
|
232
|
+
quiet. The board's red top line lists failures from the last hour whose notice was not delivered or that the conductor
|
|
233
|
+
has not yet acted on (it clears when the conductor runs `orq wait`, `orq verify` or `orq agent` for the issue).
|
|
234
|
+
`orq quota --line` prints quota in one line for any status bar. `orq ci` shows each claimed issue's PR checks (queued,
|
|
235
|
+
running, passed, failed or none, read from `gh` right then — it is a status check the user asked for, so unlike the
|
|
236
|
+
board's CI column it does not just read the cache); a failure pings the owning conductor once per commit. After
|
|
237
|
+
merges, `orq gc` lists and removes the issue worktrees left behind.
|
|
238
|
+
|
|
239
|
+
Several issues at once: one conductor per issue (ideally on different accounts). `orq claim` (`herdr-claim`) keeps two
|
|
240
|
+
conductors off the same issue.
|
|
241
|
+
|
|
242
|
+
## With herdr
|
|
243
|
+
|
|
244
|
+
herdr is optional, but it gives orq the most polish: children in their own tabs, notices typed into the conductor's
|
|
245
|
+
pane, and a few extras:
|
|
246
|
+
- `orq conductor` (or `herdr-conductor`) in a herdr pane also names the pane "conductor".
|
|
247
|
+
- `ui.toast.delivery = "system"` in herdr's config makes each finished step pop up a native OS notification; failures
|
|
248
|
+
also get a herdr notification with sound, and a CI run turning red or green pops one too.
|
|
249
|
+
- `orq quota --line` fits herdr's tab bar (a `tab_bar_right` command entry,
|
|
250
|
+
[docs/herdr.md](docs/herdr.md#quota-in-the-tab-bar)).
|
|
251
|
+
- A key for the language: `install.sh` links the `orq` herdr plugin; bind its `orq.lang-toggle` action (snippet in
|
|
252
|
+
`herdr-plugin/herdr-plugin.toml`) — e.g. `prefix+shift+l` — and `orq.board` to open the board as an overlay.
|
|
253
|
+
|
|
254
|
+
Day to day, shortcuts and several issues at once: [docs/herdr.md](docs/herdr.md).
|
|
234
255
|
|
|
235
256
|
## Skills
|
|
236
257
|
|
|
@@ -246,15 +267,15 @@ off the same issue. Details, shortcuts and scenarios: [docs/herdr.md](docs/herdr
|
|
|
246
267
|
|
|
247
268
|
| Command | What it does |
|
|
248
269
|
|---|---|
|
|
249
|
-
| `orq agent -a <agent> ...` (`herdr-agent`) | Runs an agent with a memory cap in a herdr tab, a tmux window or the background; `-r` read only, `-R <id>` continue the session, `-i` attach a file, `-b <base>` attach the diff, `-T` the agent's own screen (herdr or tmux; `-R` with it open types the follow-up into it) |
|
|
270
|
+
| `orq agent -a <agent> ...` (`herdr-agent`) | Runs an agent with a memory cap in a herdr tab, a tmux window or the background; `-r` read only, `-R <id>` continue the session, `-i` attach a file, `-b <base>` attach the diff, `-T` the agent's own screen (herdr or tmux; `-R` with it open types the follow-up into it; a finished screen closes after `[machine] tui_idle_close_min` minutes without a follow-up, default 30, `0` = never) |
|
|
250
271
|
| `orq instructions claude\|agents` | Writes the `/issue` playbook as `SKILL.md` (Claude Code) or into `AGENTS.md` (Codex, OpenCode…) |
|
|
251
272
|
| `orq notice [--list] [-n N]` | The delivery log of notices to conductors: each attempt, retry and failure (`<state>/notices.jsonl`) |
|
|
252
273
|
| `orq wait <prefix>...` | Blocks until those agents finish; prints `status=` and the answer file (any conductor, any terminal) |
|
|
253
274
|
| `orq verify ...` (`herdr-verify`) | Tests related to the diff, queued and memory-capped; failures that also fail on the base count as pre-existing |
|
|
254
275
|
| `orq board` (`herdr-board`) | Live board: queue, agents, conductors, quota, blocks, scoreboard |
|
|
255
|
-
| `herdr-conductor` | Opens the conductor in the current
|
|
256
|
-
| `herdr-claim` | Owner of each issue among conductors |
|
|
257
|
-
| `herdr-deps` | Links the worktree's dependencies to the main repo's (no reinstall) |
|
|
276
|
+
| `orq conductor [account] [model] [-r <id>]` (`herdr-conductor`) | Opens the conductor (Claude) in the current terminal with its instructions, account and model; `-r` resumes a session |
|
|
277
|
+
| `orq claim` (`herdr-claim`) | Owner of each issue among conductors |
|
|
278
|
+
| `orq deps` (`herdr-deps`) | Links the worktree's dependencies to the main repo's (no reinstall) |
|
|
258
279
|
| `orq baseline ...` (`herdr-baseline`) | Manual snapshot of the tests failing on the base (outside the flow) |
|
|
259
280
|
| `orq gc [-w <workspace>] [--yes] [--days N] [--json]` | Lists issue worktrees that are safe to remove (PR merged, or content already in the base, squash merges included); `--yes` removes them. Never touches uncommitted work, unpushed commits, a live claim or a running agent. Optional daily `orq-gc.timer` |
|
|
260
281
|
| `orq ci [-w <workspace>] [--json]` | CI/CD status per claimed issue with a PR: queued, running, passed, failed or none, collected live via `gh` right then (the cache it fills is what `orq board`'s CI column reads, never blocking on the network). A run turning red or green pops a herdr notification, and a failure also pings the owning conductor |
|
package/README.pt-BR.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center"><img src="docs/img/orq-logo.svg" alt="orq — um orc de fraque erguendo a batuta de maestro" width="304"></p>
|
|
4
4
|
|
|
5
|
-
# orq — orquestrando agentes de código
|
|
5
|
+
# orq — orquestrando agentes de código
|
|
6
6
|
|
|
7
7
|
Você descreve o trabalho numa issue do Plane. Um **conductor** (Claude, de preferência Opus) lê a issue, escolhe o
|
|
8
|
-
agente certo para cada etapa, despacha o **implementador**, os **testes** e uma **review cross-vendor**
|
|
9
|
-
[herdr](https://herdr.dev)
|
|
8
|
+
agente certo para cada etapa, despacha o **implementador**, os **testes** e uma **review cross-vendor** (cada um
|
|
9
|
+
em sua própria aba ou janela no [herdr](https://herdr.dev) ou no tmux, ou em segundo plano), e devolve a issue
|
|
10
|
+
*In Review* com um resumo no Plane. Merge, PR e deploy continuam com você.
|
|
10
11
|
|
|
11
12
|
O que o orq **não** decide por você: quais agentes você tem, quanto cada um pode gastar e como você gosta de
|
|
12
13
|
trabalhar. Tudo isso fica na sua config.
|
|
@@ -19,17 +20,18 @@ trabalhar. Tudo isso fica na sua config.
|
|
|
19
20
|
## Como funciona
|
|
20
21
|
|
|
21
22
|
```
|
|
22
|
-
You ── "/issue DEMO-123" ──► conductor (Claude in
|
|
23
|
-
│ orq
|
|
24
|
-
├─► implementer
|
|
25
|
-
├─► tests
|
|
26
|
-
│ orq
|
|
27
|
-
├─► reviewer
|
|
23
|
+
You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
|
|
24
|
+
│ orq pick impl ──► which agent/model implements (config + history + quota)
|
|
25
|
+
├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
|
|
26
|
+
├─► tests orq verify only the diff's tests · queue of one suite at a time
|
|
27
|
+
│ orq pick review ─► a reviewer from ANOTHER vendor
|
|
28
|
+
├─► reviewer orq agent -r read only · diff + criteria + test results attached
|
|
28
29
|
└─► Plane comment, state, labels
|
|
29
|
-
◄── "[HERDR-AGENT DONE] ..."
|
|
30
|
+
◄── "[HERDR-AGENT DONE] ..." typed into the conductor's herdr pane, or `orq wait` printing "[ORQ AGENT DONE] ..."
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
- **Cada etapa
|
|
33
|
+
- **Cada etapa na sua própria aba ou janela.** No herdr ou no tmux você acompanha ao vivo e a aba fecha quando
|
|
34
|
+
termina; sem eles a etapa roda em segundo plano (o `orq board` mostra). Os resultados ficam em
|
|
33
35
|
`.orquestra/<ID>/` (`.out.md`, `.log`, `.done`).
|
|
34
36
|
- **Ordem fixa em toda rodada:** implementador ok → testes sem falha nova → review. Só um `[blocker]` com
|
|
35
37
|
`arquivo:linha` e uma falha concreta vira rodada de correção; o resto vai para o comentário no Plane (com máximo
|
|
@@ -154,6 +156,9 @@ handoff_at = 85 # % da janela → o orq handoff move o
|
|
|
154
156
|
max_per_account = 2 # conta que já tem esse tanto de conductors nunca é alvo do handoff
|
|
155
157
|
reset_grace_min = 15 # toda janela acima do gatilho renova dentro desses minutos → pula a mudança
|
|
156
158
|
|
|
159
|
+
[git]
|
|
160
|
+
issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Nome de branch/worktree → ID da issue, para `orq gc` e `orq ci` — padrão mostrado; regex, grupo nomeado `issue` (senão o grupo 1)
|
|
161
|
+
|
|
157
162
|
[policy]
|
|
158
163
|
quota_limit = 90 # caps the 5h pick limit; also sets ! in `orq quota --line`
|
|
159
164
|
pacing_penalty = 0.3 # score penalty when usage at the current pace would pass the pick limit (5h by level, week: quota_week) by the reset (0 = off)
|
|
@@ -181,13 +186,12 @@ orq pick fix --same .orquestra/<ID>/impl-<repo>.pick # a fix round: the implem
|
|
|
181
186
|
```
|
|
182
187
|
|
|
183
188
|
### Idioma e tema
|
|
184
|
-
O orq fala inglês e português: mensagens da CLI, `
|
|
189
|
+
O orq fala inglês e português: mensagens da CLI, `orq board`, avisos e `install.sh`. Ele segue `[ui] lang` na sua
|
|
185
190
|
config (`"auto"` = idioma do sistema, `"en"`, `"pt"`) ou a variável de ambiente `ORQ_LANG`. Para trocar a qualquer
|
|
186
191
|
momento:
|
|
187
192
|
- `orq-lang pt` / `orq-lang en` / `orq-lang toggle` (`orq-lang` sozinho mostra o atual);
|
|
188
193
|
- a tecla `l` dentro do `orq board`;
|
|
189
|
-
- uma tecla
|
|
190
|
-
`herdr-plugin/herdr-plugin.toml`) — por exemplo, `prefix+shift+l` — e a `orq.board` para abrir o painel por cima.
|
|
194
|
+
- no herdr, uma tecla para isso: veja [Com o herdr](#com-o-herdr).
|
|
191
195
|
|
|
192
196
|
O `orq board` tem visual próprio, inspirado no portfólio [SYNTH.DECK](https://github.com/irythill/synth-deck-portfolio):
|
|
193
197
|
`[ui] theme = "gunmetal"` (padrão, grafite escuro), `"chrome"` (prata claro), `"lcd"` (verde fósforo) ou `"terminal"`
|
|
@@ -197,33 +201,51 @@ O que já está rodando continua no idioma em que começou. Os conductors sempre
|
|
|
197
201
|
|
|
198
202
|
## Uso diário
|
|
199
203
|
|
|
204
|
+
Abra o conductor em qualquer terminal, na pasta do seu workspace (a que o `orq setup` preparou):
|
|
200
205
|
```bash
|
|
201
|
-
|
|
202
|
-
cd ~/Code/<workspace> && herdr-conductor # dentro de um pane do herdr: abre o conductor (conta e modelo opcionais)
|
|
206
|
+
cd ~/Code/<workspace> && orq conductor [conta] [modelo] # qualquer terminal: carrega as instruções do conductor, o CLAUDE.md do workspace e a skill /issue
|
|
203
207
|
```
|
|
204
|
-
|
|
208
|
+
Com vários conductors, dê a cada um um nome fixo (`export ORQ_CONDUCTOR_ID=<nome>`) para o `orq claim` distingui-los.
|
|
209
|
+
Variantes:
|
|
210
|
+
- **herdr:** rode `herdr` (o servidor roda como serviço) e, num pane, o mesmo `orq conductor` (o `herdr-conductor` continua funcionando).
|
|
211
|
+
- **tmux:** rode `orq conductor` num pane; os filhos abrem como janelas do tmux.
|
|
212
|
+
- **Nenhum dos dois:** os filhos rodam em segundo plano e o conductor espera com `orq wait`.
|
|
213
|
+
|
|
214
|
+
No conductor: **`/issue DEMO-123`**. Ele responde numa linha e segue sozinho a cada sinal de conclusão
|
|
215
|
+
(`[HERDR-AGENT DONE]` num pane do herdr; fora dele, a linha `[ORQ AGENT DONE]` impressa pelo `orq wait`). Você só volta
|
|
205
216
|
quando ele pede uma decisão ou avisa que terminou.
|
|
206
217
|
|
|
207
218
|
Para ver tudo de uma vez, deixe o **`orq board`** aberto numa aba ou janela (qualquer terminal, qualquer SO; o
|
|
208
219
|
`herdr-board` o chama): fila de testes, agentes rodando (modelo, memória contra o limite, última ação), conductors e
|
|
209
220
|
suas issues, memória, quota, CI/CD de cada issue reivindicada, bloqueios e o placar de 7 dias. `--once` imprime uma
|
|
210
|
-
vez, `--json` para scripts.
|
|
211
|
-
|
|
212
|
-
conductor (entregue pelo orq com novas tentativas e log, `orq notice`), você recebe
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
vez, `--json` para scripts.
|
|
222
|
+
Uma falha (testes vermelhos, agente `status=fail`, CI vermelho) também é **barulhenta**: além da linha digitada no pane
|
|
223
|
+
do conductor (entregue pelo orq com novas tentativas e log, `orq notice`), você recebe um título `✗` no pane do
|
|
224
|
+
conductor (herdr) e, no Linux, uma notificação de desktop (`[ui] desktop_notify = false` desliga essa; precisa do
|
|
225
|
+
`notify-send`). Sucessos ficam quietos. A linha vermelha no topo do board lista as falhas da última hora cujo aviso não
|
|
226
|
+
foi entregue ou que o conductor ainda não tratou (some quando ele roda `orq wait`, `orq verify` ou `orq agent` para a
|
|
227
|
+
issue).
|
|
228
|
+
O `orq quota --line` imprime a cota numa linha para qualquer barra de status. O `orq ci` mostra os checks do PR de cada
|
|
229
|
+
issue reivindicada (na fila, rodando, passou, falhou ou nenhum, lido via `gh` na hora — é uma consulta que o usuário
|
|
230
|
+
pediu agora, então, diferente da coluna de CI do board, não lê só o cache); uma falha também avisa o conductor dono num
|
|
231
|
+
aviso só por commit. Depois dos merges, o `orq gc` lista e remove as worktrees de issues que ficaram para trás.
|
|
232
|
+
|
|
233
|
+
Várias issues ao mesmo tempo: um conductor por issue (de preferência em contas diferentes). O `orq claim`
|
|
234
|
+
(`herdr-claim`) impede que dois conductors peguem a mesma issue.
|
|
235
|
+
|
|
236
|
+
## Com o herdr
|
|
237
|
+
|
|
238
|
+
O herdr é opcional, mas é com ele que o orq fica mais redondo: filhos em abas próprias, avisos digitados no pane do
|
|
239
|
+
conductor e alguns extras:
|
|
240
|
+
- `orq conductor` (ou `herdr-conductor`) num pane do herdr também dá o nome "conductor" ao pane.
|
|
241
|
+
- `ui.toast.delivery = "system"` na config do herdr faz cada etapa concluída gerar uma notificação nativa do sistema;
|
|
242
|
+
falhas também geram uma notificação do herdr com som, e um CI que fica verde ou vermelho também gera uma.
|
|
243
|
+
- O `orq quota --line` cabe na barra de abas do herdr (uma entrada `command` no `tab_bar_right`,
|
|
244
|
+
[docs/pt-BR/herdr.md](docs/pt-BR/herdr.md#cota-na-barra-de-abas)).
|
|
245
|
+
- Uma tecla para o idioma: o `install.sh` liga o plugin `orq` do herdr; associe a ação `orq.lang-toggle` (trecho pronto
|
|
246
|
+
em `herdr-plugin/herdr-plugin.toml`) — por exemplo, `prefix+shift+l` — e a `orq.board` para abrir o painel por cima.
|
|
247
|
+
|
|
248
|
+
Dia a dia, atalhos e várias issues ao mesmo tempo: [docs/pt-BR/herdr.md](docs/pt-BR/herdr.md).
|
|
227
249
|
|
|
228
250
|
## Skills
|
|
229
251
|
|
|
@@ -239,15 +261,15 @@ dois conductors peguem a mesma issue. Detalhes, atalhos e cenários: [docs/pt-BR
|
|
|
239
261
|
|
|
240
262
|
| Comando | O que faz |
|
|
241
263
|
|---|---|
|
|
242
|
-
| `orq agent -a <agent> ...` (`herdr-agent`) | Roda um agente com limite de memória numa aba do herdr, numa janela do tmux ou em segundo plano; `-r` só leitura, `-R <id>` continua a sessão, `-i` anexa um arquivo, `-b <base>` anexa o diff, `-T` a tela do próprio agente (herdr ou tmux; `-R` com ela aberta digita o follow-up nela) |
|
|
264
|
+
| `orq agent -a <agent> ...` (`herdr-agent`) | Roda um agente com limite de memória numa aba do herdr, numa janela do tmux ou em segundo plano; `-r` só leitura, `-R <id>` continua a sessão, `-i` anexa um arquivo, `-b <base>` anexa o diff, `-T` a tela do próprio agente (herdr ou tmux; `-R` com ela aberta digita o follow-up nela; a tela que terminou fecha após `[machine] tui_idle_close_min` minutos sem follow-up, padrão 30, `0` = nunca) |
|
|
243
265
|
| `orq instructions claude\|agents` | Escreve o playbook do `/issue` como `SKILL.md` (Claude Code) ou dentro do `AGENTS.md` (Codex, OpenCode…) |
|
|
244
266
|
| `orq notice [--list] [-n N]` | O log de entrega dos avisos aos conductors: cada tentativa, nova tentativa e falha (`<state>/notices.jsonl`) |
|
|
245
267
|
| `orq wait <prefixo>...` | Bloqueia até esses agentes terminarem; imprime `status=` e o arquivo da resposta (qualquer conductor, qualquer terminal) |
|
|
246
268
|
| `orq verify ...` (`herdr-verify`) | Testes relacionados ao diff, em fila e com limite de memória; falhas que também falham na base contam como pré-existentes |
|
|
247
269
|
| `orq board` (`herdr-board`) | Painel ao vivo: fila, agentes, conductors, cota, bloqueios, placar |
|
|
248
|
-
| `herdr-conductor` | Abre o conductor no
|
|
249
|
-
| `herdr-claim` | Dono de cada issue entre os conductors |
|
|
250
|
-
| `herdr-deps` | Liga as dependências do worktree às do repo principal (sem reinstalar) |
|
|
270
|
+
| `orq conductor [conta] [modelo] [-r <id>]` (`herdr-conductor`) | Abre o conductor (Claude) no terminal atual com as instruções, conta e modelo; `-r` retoma uma sessão |
|
|
271
|
+
| `orq claim` (`herdr-claim`) | Dono de cada issue entre os conductors |
|
|
272
|
+
| `orq deps` (`herdr-deps`) | Liga as dependências do worktree às do repo principal (sem reinstalar) |
|
|
251
273
|
| `orq baseline ...` (`herdr-baseline`) | Snapshot manual dos testes que falham na base (fora do fluxo) |
|
|
252
274
|
| `orq gc [-w <workspace>] [--yes] [--days N] [--json]` | Lista worktrees de issues que podem ser removidas (PR mergeado, ou conteúdo já na base, inclusive squash merge); `--yes` as remove. Nunca mexe em trabalho não commitado, commit fora do remote, claim ativo ou agente rodando. `orq-gc.timer` diário opcional |
|
|
253
275
|
| `orq ci [-w <workspace>] [--json]` | Status de CI/CD de cada issue reivindicada com PR: na fila, rodando, passou, falhou ou nenhum, coletado na hora via `gh` (o cache que ele preenche é o que a coluna de CI do `orq board` lê, sem nunca travar na rede). Quando vira verde ou vermelho sai uma notificação do herdr, e uma falha também avisa o conductor dono |
|
package/dist/agents.js
CHANGED
|
@@ -47,8 +47,8 @@ export const BUILTIN_AGENTS = {
|
|
|
47
47
|
turn_hook: "opencode",
|
|
48
48
|
},
|
|
49
49
|
antigravity: {
|
|
50
|
-
// --sandbox + accept-edits (no skip-permissions); headless denies commands outside agy's allowlist
|
|
51
|
-
//
|
|
50
|
+
// --sandbox + accept-edits (no skip-permissions); headless denies commands outside agy's allowlist (see agy-impl.json);
|
|
51
|
+
// a run that only had commands denied still counts when the worktree changed (see the antigravity format). The prompt goes in a file in its folder (excluded from git) and -p only says "read it".
|
|
52
52
|
run: `${AGY} {mode} -p {prompt_hint}`, resume: `${AGY} --conversation {session} {mode} -p {prompt_hint}`,
|
|
53
53
|
readonly: "--mode plan", write: "--mode accept-edits --add-dir {gitdir}",
|
|
54
54
|
format: "antigravity", throwaway: true, prompt_in_dir: true,
|
|
@@ -154,7 +154,14 @@ export function expandTemplate(t, vars) {
|
|
|
154
154
|
if (g >= 0)
|
|
155
155
|
while (j < words.length && words[j].group === g)
|
|
156
156
|
j++;
|
|
157
|
-
|
|
157
|
+
const got = expandWords(words.slice(i, j).map((w) => w.text), vars, g >= 0);
|
|
158
|
+
// `--flag {placeholder}` with the placeholder empty: drop the flag too, or it would swallow the next argument.
|
|
159
|
+
const ph = g < 0 ? /^\{(\w+)(?::[^}]*)?\}$/.exec(words[i].text) : null, prev = words[i - 1];
|
|
160
|
+
if (!got.length && ph && !Array.isArray(vars[ph[1]]) && prev && prev.group < 0 && /^-[^\s{]/.test(prev.text)
|
|
161
|
+
&& out[out.length - 1] === prev.text)
|
|
162
|
+
out.pop();
|
|
163
|
+
else
|
|
164
|
+
out.push(...got);
|
|
158
165
|
i = j;
|
|
159
166
|
}
|
|
160
167
|
return out;
|
|
@@ -206,16 +213,83 @@ export const FORMATS = {
|
|
|
206
213
|
return s.step_type === "tool" && s.state === "DONE" ? "\n· tool\n" : undefined;
|
|
207
214
|
},
|
|
208
215
|
finish(log) {
|
|
209
|
-
const
|
|
216
|
+
const evs = jsonLines(log);
|
|
217
|
+
const res = evs.filter((j) => j.event === "result").map((j) => j.result ?? {});
|
|
210
218
|
let answer = lastOf(res.map((r) => String(r.response ?? ""))) ?? "";
|
|
211
|
-
// An agy error (e.g. a denied command) is a failure, even with an answer.
|
|
212
219
|
const errs = log.split(/\r?\n/).filter((l) => l.startsWith("jetski: "));
|
|
220
|
+
// Denied commands: ERROR steps with the command line, plus denied_actions on the result.
|
|
221
|
+
const denied = [];
|
|
222
|
+
let text = "", textId, lastCmd = "", sawResult = false; // lastCmd: last CommandLine streamed before the result
|
|
223
|
+
const hinted = res.some((r) => Array.isArray(r.denied_actions) && r.denied_actions.length) || errs.some((l) => /permission/.test(l));
|
|
224
|
+
for (const e of evs) {
|
|
225
|
+
if (e.event === "result")
|
|
226
|
+
sawResult = true;
|
|
227
|
+
const s = e.event === "step_update" ? e.step_update : undefined;
|
|
228
|
+
if (!s)
|
|
229
|
+
continue;
|
|
230
|
+
const seen = (s.tool_info ?? e.tool_info)?.parameters?.CommandLine;
|
|
231
|
+
if (!sawResult && typeof seen === "string" && seen.trim())
|
|
232
|
+
lastCmd = seen.trim();
|
|
233
|
+
if (s.step_type === "agent_response") {
|
|
234
|
+
const id = s.step_id ?? s.step_index ?? s.id;
|
|
235
|
+
if (id !== textId) {
|
|
236
|
+
textId = id;
|
|
237
|
+
if (s.text_delta)
|
|
238
|
+
text = "";
|
|
239
|
+
}
|
|
240
|
+
text += String(s.text_delta ?? "");
|
|
241
|
+
}
|
|
242
|
+
if (s.state === "ERROR") {
|
|
243
|
+
const ti = s.tool_info ?? e.tool_info, cmd = ti?.parameters?.CommandLine, msg = String(s.error?.message ?? "");
|
|
244
|
+
if (/permission check failed|not allowed|denied/i.test(msg) || (cmd && !msg && hinted)) {
|
|
245
|
+
const d = `${cmd ? `\`${cmd}\`` : "(command)"}${msg ? ` — ${msg}` : ""}`;
|
|
246
|
+
if (!denied.includes(d))
|
|
247
|
+
denied.push(d);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const acts = res.flatMap((r) => (Array.isArray(r.denied_actions) ? r.denied_actions : []));
|
|
252
|
+
const wasDenied = denied.length > 0 || acts.length > 0 || errs.some((l) => /permission/.test(l));
|
|
253
|
+
// denied_actions only says "RunCommand": the command is the last one the stream showed before the result.
|
|
254
|
+
if (!denied.length && lastCmd && acts.length && acts.every((a) => a.display_name === "RunCommand"))
|
|
255
|
+
denied.push(`denied: ${lastCmd}`);
|
|
256
|
+
if (wasDenied && !answer.trim())
|
|
257
|
+
answer = text.trim();
|
|
258
|
+
if (wasDenied) {
|
|
259
|
+
answer += `${answer ? "\n\n" : ""}## Denied commands\n` +
|
|
260
|
+
"agy denied commands outside its allowlist (headless, nobody to approve). Retry with a different command, or add a glob rule such as command(/usr/bin/*) to agy's allowlist (~/.config/orq/agy-impl.json, see examples/agy-impl.json) -- usually that is the fix.\n" +
|
|
261
|
+
(denied.length ? denied.map((d) => `- ${d}`).join("\n") : acts.map((a) => `- ${a.action ?? "command"} (${a.display_name ?? "?"})`).join("\n")) + "\n";
|
|
262
|
+
}
|
|
213
263
|
if (errs.length)
|
|
214
264
|
answer += (answer ? "\n" : "") + errs.join("\n") + "\n";
|
|
215
|
-
|
|
265
|
+
const exhausted = agyExhausted(log, res);
|
|
266
|
+
return { exhausted, session: lastOf(res.map((r) => r.conversation_id).filter(Boolean)), answer, rc: errs.length || wasDenied ? 3 : undefined,
|
|
267
|
+
denied: wasDenied ? (denied.length ? denied : ["(command)"]) : undefined };
|
|
216
268
|
},
|
|
217
269
|
},
|
|
218
270
|
};
|
|
271
|
+
/** "Resets in 123h12m32s" → seconds (undefined without such a duration). */
|
|
272
|
+
export function parseResetIn(text) {
|
|
273
|
+
const r = /Resets in\s+(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?/i.exec(text);
|
|
274
|
+
if (!r || !(r[1] || r[2] || r[3]))
|
|
275
|
+
return undefined;
|
|
276
|
+
return Number(r[1] ?? 0) * 3600 + Number(r[2] ?? 0) * 60 + Number(r[3] ?? 0);
|
|
277
|
+
}
|
|
278
|
+
/** agy's quota failure (RESOURCE_EXHAUSTED / "quota reached"), from the result's error or the AGY_ERROR line. */
|
|
279
|
+
function agyExhausted(log, res) {
|
|
280
|
+
const msgs = res.filter((r) => r.status === "ERROR" || r.error).map((r) => String(r.error ?? ""));
|
|
281
|
+
for (const l of log.split(/\r?\n/)) {
|
|
282
|
+
if (!l.startsWith("AGY_ERROR:"))
|
|
283
|
+
continue;
|
|
284
|
+
const j = json(l.slice(10).trim());
|
|
285
|
+
if (j)
|
|
286
|
+
msgs.push(`${j.status ?? ""} ${j.short_error ?? ""}`);
|
|
287
|
+
}
|
|
288
|
+
const text = msgs.find((t) => /RESOURCE_EXHAUSTED|quota reached/i.test(t));
|
|
289
|
+
if (!text)
|
|
290
|
+
return undefined;
|
|
291
|
+
return { secs: parseResetIn(text), message: text.replace(/^.*?(?:\(code 429\):\s*)/, "").trim() };
|
|
292
|
+
}
|
|
219
293
|
/** The model in an agent's argv (-m / --model / --model=x), without the provider prefix. */
|
|
220
294
|
export function argvModel(a) {
|
|
221
295
|
const i = a.findIndex((x) => x === "-m" || x === "--model");
|
package/dist/ci.js
CHANGED
|
@@ -15,6 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { aliveOwner } from "./commands/claim.js";
|
|
16
16
|
import { writeAtomic } from "./fsutil.js";
|
|
17
17
|
import { m } from "./i18n.js";
|
|
18
|
+
import { issueFromName, issuePattern } from "./issue.js";
|
|
18
19
|
import { alive } from "./lock.js";
|
|
19
20
|
import { stateDir } from "./paths.js";
|
|
20
21
|
import { notify } from "./testrun.js";
|
|
@@ -93,11 +94,10 @@ export function listWorktrees(ws) {
|
|
|
93
94
|
return out;
|
|
94
95
|
}
|
|
95
96
|
/** The repo + branch an issue's worktree carries, the same match `orq gc` uses: a worktree branch ending in
|
|
96
|
-
* "plane
|
|
97
|
+
* the issue ID (git.issue_pattern; any "plane-" prefix or suffix is ignored) under <ws>/.worktrees. An issue can have one per repo (several repos in play).
|
|
97
98
|
* `all` lets a caller looping over several issues pass its own listWorktrees(ws) once instead of one per issue. */
|
|
98
|
-
export function issueBranches(ws, issue, all = listWorktrees(ws)) {
|
|
99
|
-
|
|
100
|
-
return all.filter((b) => b.branch.toLowerCase().endsWith(suffix));
|
|
99
|
+
export function issueBranches(ws, issue, all = listWorktrees(ws), pattern) {
|
|
100
|
+
return all.filter((b) => issueFromName(b.branch, pattern)?.toLowerCase() === issue.toLowerCase());
|
|
101
101
|
}
|
|
102
102
|
// GitHub's statusCheckRollup mixes CheckRun (status/conclusion, the GitHub Actions / Checks API shape) with the
|
|
103
103
|
// legacy commit-status shape (StatusContext: just `state`, no status/conclusion at all) for repos whose CI still
|
|
@@ -204,8 +204,8 @@ function notifyDesktop(issue, repo, state) {
|
|
|
204
204
|
export function collectCi(ws, hooks = {}) {
|
|
205
205
|
// Detached: collectCi() must never block on a conductor stuck in a dialog (herdr-notify waits up to 30 min).
|
|
206
206
|
const desktop = hooks.desktop ?? notifyDesktop, toOwner = hooks.owner ?? ((pane, line) => notify(pane, line)), notice = hooks.notice ?? ((l) => console.log(l));
|
|
207
|
-
const now = Date.now(), worktrees = listWorktrees(ws);
|
|
208
|
-
const found = claimedIssues(ws).flatMap(({ id, owner, kind }) => issueBranches(ws, id, worktrees).map(({ repo, branch }) => ({ id, owner, kind, repo, branch, ...prStatus(join(ws, repo), branch) })));
|
|
207
|
+
const now = Date.now(), worktrees = listWorktrees(ws), pattern = issuePattern(ws);
|
|
208
|
+
const found = claimedIssues(ws).flatMap(({ id, owner, kind }) => issueBranches(ws, id, worktrees, pattern).map(({ repo, branch }) => ({ id, owner, kind, repo, branch, ...prStatus(join(ws, repo), branch) })));
|
|
209
209
|
const toNotify = [];
|
|
210
210
|
const mine = withCacheLock(() => {
|
|
211
211
|
const cache = loadCache(), mine = [];
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { block, USAGE as BLOCK } from "./commands/block.js";
|
|
|
11
11
|
import { claim, USAGE as CLAIM } from "./commands/claim.js";
|
|
12
12
|
import { ci, USAGE as CI } from "./commands/ci.js";
|
|
13
13
|
import { completion, USAGE as COMPLETION } from "./commands/completion.js";
|
|
14
|
+
import { conductor, USAGE as CONDUCTOR } from "./commands/conductor.js";
|
|
14
15
|
import { config, USAGE as CONFIG } from "./commands/config.js";
|
|
15
16
|
import { deps, USAGE as DEPS } from "./commands/deps.js";
|
|
16
17
|
import { gc, USAGE as GC } from "./commands/gc.js";
|
|
@@ -28,7 +29,7 @@ import { setup, USAGE as SETUP } from "./commands/setup.js";
|
|
|
28
29
|
import { stats, USAGE as STATS } from "./commands/stats.js";
|
|
29
30
|
import { verify, USAGE as VERIFY } from "./commands/verify.js";
|
|
30
31
|
import { wait, USAGE as WAIT } from "./commands/wait.js";
|
|
31
|
-
const COMMANDS = { config, set, board, lang, block, quota, pick, stats, rounds, setup, lock, claim, deps, gc, ci, handoff, run, agent, wait, instructions, verify, baseline, notice: noticeCmd,
|
|
32
|
+
const COMMANDS = { config, conductor, set, board, lang, block, quota, pick, stats, rounds, setup, lock, claim, deps, gc, ci, handoff, run, agent, wait, instructions, verify, baseline, notice: noticeCmd,
|
|
32
33
|
completion: (a) => completion(a, Object.keys(COMMANDS)) };
|
|
33
34
|
const usage = () => `orq <command> [args]
|
|
34
35
|
|
|
@@ -50,6 +51,7 @@ ${VERIFY}
|
|
|
50
51
|
${BASELINE}
|
|
51
52
|
${INSTRUCTIONS}
|
|
52
53
|
${NOTICE}
|
|
54
|
+
${CONDUCTOR}
|
|
53
55
|
${CLAIM}
|
|
54
56
|
${DEPS}
|
|
55
57
|
${GC}
|