@irythill/orq 0.2.0 → 0.4.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/README.md +49 -12
- package/README.pt-BR.md +41 -12
- package/dist/agents.js +12 -0
- package/dist/ci.js +239 -0
- package/dist/cli.js +7 -1
- package/dist/commands/agent.js +270 -29
- package/dist/commands/block.js +20 -3
- package/dist/commands/board.js +87 -8
- package/dist/commands/ci.js +48 -0
- package/dist/commands/claim.js +148 -12
- package/dist/commands/completion.js +3 -2
- package/dist/commands/gc.js +1 -1
- package/dist/commands/handoff.js +259 -0
- package/dist/commands/pick.js +18 -3
- package/dist/commands/quota.js +34 -9
- package/dist/commands/rounds.js +58 -0
- package/dist/config.js +4 -1
- package/dist/identity.js +55 -0
- package/dist/pick.js +106 -9
- package/dist/process.js +19 -11
- package/dist/quota.js +83 -21
- package/dist/rounds.js +167 -0
- package/dist/terminal.js +26 -0
- package/dist/testrun.js +26 -5
- package/package.json +1 -1
- package/skills/issue/SKILL.md +47 -10
- package/skills/orq-setup/SKILL.md +1 -1
- package/templates/workspace.CLAUDE.md +7 -0
package/README.md
CHANGED
|
@@ -31,8 +31,12 @@ You ── "/issue DEMO-123" ──► conductor (Claude in herdr)
|
|
|
31
31
|
|
|
32
32
|
- **Every step in a herdr tab.** You watch it live and the tab closes when it's done. Results stay in
|
|
33
33
|
`.orquestra/<ID>/` (`.out.md`, `.log`, `.done`).
|
|
34
|
-
- **Fixed order every round:** implementer ok → tests with no new failure → review.
|
|
35
|
-
round
|
|
34
|
+
- **Fixed order every round:** implementer ok → tests with no new failure → review. Only a `[blocker]` with
|
|
35
|
+
`file:line` and a concrete failure becomes a fix round; the rest goes into the Plane comment (configurable maximum).
|
|
36
|
+
- **Fix rounds converge.** A fix continues the implementer's session on the same model (`orq pick fix --same`, effort
|
|
37
|
+
one level up), fixes only the listed blockers with a test each, and the next review, by the same reviewer, checks
|
|
38
|
+
those blockers and the fix's own diff instead of starting over. The implementer ends with a `## Decisions` section, so
|
|
39
|
+
a deliberate choice doesn't come back as a blocker.
|
|
36
40
|
- **Nobody picks an agent by gut feeling.** `orq-pick` decides from data: what each agent delivered in the history,
|
|
37
41
|
how many tokens it spent, how long it took, the current quota, and a rotation so no agent gets all the work.
|
|
38
42
|
- **The machine stays up.** Each agent runs under its own memory cap; tests wait in a queue.
|
|
@@ -42,8 +46,8 @@ You ── "/issue DEMO-123" ──► conductor (Claude in herdr)
|
|
|
42
46
|
| Agent | CLI | Possible roles | Notes |
|
|
43
47
|
|---|---|---|---|
|
|
44
48
|
| Claude Code | `claude` | conductor, implement, review | several accounts (`claude-accounts`); Opus as conductor |
|
|
45
|
-
| Codex | `codex` | implement, review | sandbox without network: the conductor runs `git fetch
|
|
46
|
-
| OpenCode | `opencode` | implement, review | any provider (e.g. MiniMax) |
|
|
49
|
+
| Codex | `codex` | implement, review | sandbox without network: the conductor runs `git fetch`; quota from its session files (the free plan's 30-day window included) |
|
|
50
|
+
| OpenCode | `opencode` | implement, review | any provider (e.g. MiniMax); on MiniMax, quota from the Token Plan API with your key |
|
|
47
51
|
| Antigravity | `agy` | review | headless denies commands outside its allowlist: it reviews by reading the attached diff |
|
|
48
52
|
| GitHub Copilot | `copilot` | second opinion | small allowances on some plans: occasional use |
|
|
49
53
|
|
|
@@ -105,7 +109,7 @@ your config.
|
|
|
105
109
|
| Flag | What it does |
|
|
106
110
|
|---|---|
|
|
107
111
|
| `--dry-run` | print what it would do, change nothing |
|
|
108
|
-
| `--uninstall` | remove only the links it creates, and only those pointing into this repo (config and history stay
|
|
112
|
+
| `--uninstall` | remove only the links it creates, and only those pointing into this repo, and disable the orq systemd user timers (`systemctl --user disable --now`); config and history stay |
|
|
109
113
|
|
|
110
114
|
## Configuration
|
|
111
115
|
|
|
@@ -128,8 +132,13 @@ vendor = "openai"
|
|
|
128
132
|
roles = ["impl", "fix", "review"]
|
|
129
133
|
quota = ["codex"] # name in agent-quota's output
|
|
130
134
|
[agents.codex.models.impl]
|
|
131
|
-
simple = "gpt-6-sol@low" # model@effort
|
|
132
|
-
|
|
135
|
+
simple = "gpt-6-sol@low" # model@effort, per issue level
|
|
136
|
+
medium = "gpt-6-sol@medium"
|
|
137
|
+
heavy = "gpt-6.1-sol@high"
|
|
138
|
+
[agents.codex.models.review]
|
|
139
|
+
simple = "gpt-6-sol@low" # reviews scale with the issue too
|
|
140
|
+
default = "gpt-6-sol@medium"
|
|
141
|
+
heavy = "gpt-6-sol@high"
|
|
133
142
|
|
|
134
143
|
[agents.opencode]
|
|
135
144
|
vendor = "minimax"
|
|
@@ -137,8 +146,25 @@ roles = ["review"] # here it only reviews; for someone els
|
|
|
137
146
|
[agents.opencode.models.review]
|
|
138
147
|
default = "minimax/MiniMax-M3"
|
|
139
148
|
|
|
149
|
+
[agents.corvus] # a review tool, not a chat: no prompt, no session
|
|
150
|
+
vendor = "minimax" # the vendor of the model it runs, for the cross-vendor rule
|
|
151
|
+
roles = ["review"]
|
|
152
|
+
run = "corvus --markdown -b {base}" # {base} = orq agent -b; it reads the diff itself
|
|
153
|
+
write = false
|
|
154
|
+
ok_exit = [0, 1] # exit 1 = findings, still a verdict
|
|
155
|
+
no_review = "Nada para revisar" # matched on the answer's first line (or a short answer): not a verdict, status=fail
|
|
156
|
+
# filter = "jq -r '...'" # optional: its output → [blocker]/[suggestion] lines
|
|
157
|
+
|
|
158
|
+
[conductor]
|
|
159
|
+
handoff_at = 85 # % of the window → orq handoff moves the conductor to another account
|
|
160
|
+
max_per_account = 2 # an account already holding this many conductors is never a handoff target
|
|
161
|
+
reset_grace_min = 15 # every window over the trigger resetting within this many minutes skips the move
|
|
162
|
+
|
|
140
163
|
[policy]
|
|
141
|
-
|
|
164
|
+
quota_week = 95 # ≥ 95% of the week → agent out
|
|
165
|
+
quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
|
|
166
|
+
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)
|
|
167
|
+
# [agents.claude] headroom = 25 # points kept for conductors: children leave the account at limit − 25
|
|
142
168
|
[policy.weights]
|
|
143
169
|
quality = 0.40 # did it deliver? (impl: approved on the first review)
|
|
144
170
|
tokens = 0.45 # did it spend little?
|
|
@@ -148,7 +174,8 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
148
174
|
|
|
149
175
|
### How `orq-pick` chooses
|
|
150
176
|
1. **Filters:** only agents with that role, under the quota limit, not blocked (`orq-block`) and, for reviews, from a
|
|
151
|
-
vendor other than the implementer's. With several accounts, it
|
|
177
|
+
vendor other than the implementer's. With several accounts, it prefers one whose pace stays within the limit, then
|
|
178
|
+
the least used.
|
|
152
179
|
2. **Scores:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. The numbers come
|
|
153
180
|
from the history (`orq-stats`); an agent with few runs uses the config's `prior` and gets an exploration bonus.
|
|
154
181
|
3. **`reserve = true` agents** (e.g. Claude, which shares quota with the conductor) only come in when nothing else fits.
|
|
@@ -157,6 +184,7 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
157
184
|
orq-pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
|
|
158
185
|
orq-stats --days 30 # scoreboard: delivery, approval, time and tokens per agent and model
|
|
159
186
|
orq-block antigravity --days 3 # out of the picks (e.g. weekly quota gone, no collector for it)
|
|
187
|
+
orq pick fix --same .orquestra/<ID>/impl-<repo>.pick # a fix round: the implementer's agent/model/account, effort +1
|
|
160
188
|
```
|
|
161
189
|
|
|
162
190
|
### Language and theme
|
|
@@ -186,8 +214,14 @@ You only come back when it asks for a decision or says it's done.
|
|
|
186
214
|
|
|
187
215
|
To see everything at once, keep **`orq board`** open in a tab or window (any terminal, any OS; `herdr-board` calls it):
|
|
188
216
|
test queue, running agents (model, memory against the cap, last action), conductors and their issues, memory, quota,
|
|
189
|
-
blocks and the 7-day scoreboard. `--once` prints it once, `--json` for scripts. With
|
|
217
|
+
CI/CD per claimed issue, blocks and the 7-day scoreboard. `--once` prints it once, `--json` for scripts. With
|
|
190
218
|
`ui.toast.delivery = "system"` in herdr's config, each finished step also pops up a native OS notification.
|
|
219
|
+
For quota without opening the board, `orq quota --line` fits herdr's tab bar (a `tab_bar_right` command entry,
|
|
220
|
+
[docs/herdr.md](docs/herdr.md#quota-in-the-tab-bar)). `orq ci` shows each claimed issue's PR checks (queued, running,
|
|
221
|
+
passed, failed or none, read from `gh` right then — it is a status check the user asked for, so unlike the board's
|
|
222
|
+
CI column it does not just read the cache); a run turning red or green also pops a herdr notification, and a
|
|
223
|
+
failure pings the owning conductor once per commit. After merges, `orq gc` lists and removes the issue worktrees
|
|
224
|
+
left behind.
|
|
191
225
|
|
|
192
226
|
Several issues at once: one conductor per issue (ideally on different accounts). `herdr-claim` keeps two conductors
|
|
193
227
|
off the same issue. Details, shortcuts and scenarios: [docs/herdr.md](docs/herdr.md).
|
|
@@ -206,7 +240,7 @@ off the same issue. Details, shortcuts and scenarios: [docs/herdr.md](docs/herdr
|
|
|
206
240
|
|
|
207
241
|
| Command | What it does |
|
|
208
242
|
|---|---|
|
|
209
|
-
| `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
|
|
243
|
+
| `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) |
|
|
210
244
|
| `orq instructions claude\|agents` | Writes the `/issue` playbook as `SKILL.md` (Claude Code) or into `AGENTS.md` (Codex, OpenCode…) |
|
|
211
245
|
| `orq wait <prefix>...` | Blocks until those agents finish; prints `status=` and the answer file (any conductor, any terminal) |
|
|
212
246
|
| `orq verify ...` (`herdr-verify`) | Tests related to the diff, queued and memory-capped; failures that also fail on the base count as pre-existing |
|
|
@@ -215,7 +249,10 @@ off the same issue. Details, shortcuts and scenarios: [docs/herdr.md](docs/herdr
|
|
|
215
249
|
| `herdr-claim` | Owner of each issue among conductors |
|
|
216
250
|
| `herdr-deps` | Links the worktree's dependencies to the main repo's (no reinstall) |
|
|
217
251
|
| `orq baseline ...` (`herdr-baseline`) | Manual snapshot of the tests failing on the base (outside the flow) |
|
|
218
|
-
| `orq gc [-w <workspace>] [--yes]` | Lists issue worktrees that are safe to remove; `--yes` removes them |
|
|
252
|
+
| `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` |
|
|
253
|
+
| `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 |
|
|
254
|
+
| `orq rounds <ID> [-w <workspace>] [--json]` | Watchdog without an LLM: fix rounds vs `policy.max_rounds`, time per round, reviewer findings repeated across rounds, alert ok / warn / stop. The board marks each issue (`r2/2 ⚠`) |
|
|
255
|
+
| `orq quota --line` | One line of quota for a status bar (`work 64 · home 94! · codex 81`), from the board's cache, never the network |
|
|
219
256
|
| `orq setup` | First configuration and install: environment, skills for Claude, config, conductor workspace |
|
|
220
257
|
| `orq completion bash\|zsh\|fish\|powershell` | Prints the shell completion script (see Install) |
|
|
221
258
|
| `orq <config\|set\|lang\|block\|quota>` | The cross-platform CLI (TypeScript); the `orq-*` and `agent-quota` names below call it |
|
package/README.pt-BR.md
CHANGED
|
@@ -31,8 +31,13 @@ You ── "/issue DEMO-123" ──► conductor (Claude in herdr)
|
|
|
31
31
|
|
|
32
32
|
- **Cada etapa numa aba do herdr.** Você acompanha ao vivo e a aba fecha quando termina. Os resultados ficam em
|
|
33
33
|
`.orquestra/<ID>/` (`.out.md`, `.log`, `.done`).
|
|
34
|
-
- **Ordem fixa em toda rodada:** implementador ok → testes sem falha nova → review.
|
|
35
|
-
rodada de correção
|
|
34
|
+
- **Ordem fixa em toda rodada:** implementador ok → testes sem falha nova → review. Só um `[blocker]` com
|
|
35
|
+
`arquivo:linha` e uma falha concreta vira rodada de correção; o resto vai para o comentário no Plane (com máximo
|
|
36
|
+
configurável).
|
|
37
|
+
- **As rodadas convergem.** O fix continua a sessão do implementador no mesmo modelo (`orq pick fix --same`, esforço
|
|
38
|
+
um nível acima), corrige só os blockers listados, com um teste para cada, e o review seguinte, do mesmo reviewer,
|
|
39
|
+
confere esses blockers e o diff do próprio fix em vez de recomeçar. O implementador termina com uma seção
|
|
40
|
+
`## Decisions`, para que uma escolha proposital não volte como blocker.
|
|
36
41
|
- **Ninguém escolhe agente no feeling.** O `orq-pick` decide com base em dados: o que cada agente entregou no
|
|
37
42
|
histórico, quantos tokens gastou, quanto tempo levou, a quota atual e um rodízio para nenhum agente ficar com todo o
|
|
38
43
|
trabalho.
|
|
@@ -43,8 +48,8 @@ You ── "/issue DEMO-123" ──► conductor (Claude in herdr)
|
|
|
43
48
|
| Agente | CLI | Papéis possíveis | Observações |
|
|
44
49
|
|---|---|---|---|
|
|
45
50
|
| Claude Code | `claude` | conductor, implement, review | várias contas (`claude-accounts`); Opus como conductor |
|
|
46
|
-
| Codex | `codex` | implement, review | sandbox sem rede: o conductor roda o `git fetch
|
|
47
|
-
| OpenCode | `opencode` | implement, review | qualquer provider (ex.: MiniMax) |
|
|
51
|
+
| Codex | `codex` | implement, review | sandbox sem rede: o conductor roda o `git fetch`; cota lida dos arquivos de sessão (inclusive a janela de 30 dias do plano free) |
|
|
52
|
+
| OpenCode | `opencode` | implement, review | qualquer provider (ex.: MiniMax); na MiniMax, cota pela API do Token Plan com a sua chave |
|
|
48
53
|
| Antigravity | `agy` | review | em modo headless nega comandos fora da allowlist: faz a review lendo o diff anexado |
|
|
49
54
|
| GitHub Copilot | `copilot` | segunda opinião | cota pequena em alguns planos: uso ocasional |
|
|
50
55
|
|
|
@@ -130,8 +135,13 @@ vendor = "openai"
|
|
|
130
135
|
roles = ["impl", "fix", "review"]
|
|
131
136
|
quota = ["codex"] # name in agent-quota's output
|
|
132
137
|
[agents.codex.models.impl]
|
|
133
|
-
simple = "gpt-6-sol@low" # model@effort
|
|
134
|
-
|
|
138
|
+
simple = "gpt-6-sol@low" # model@effort, per issue level
|
|
139
|
+
medium = "gpt-6-sol@medium"
|
|
140
|
+
heavy = "gpt-6.1-sol@high"
|
|
141
|
+
[agents.codex.models.review]
|
|
142
|
+
simple = "gpt-6-sol@low" # reviews scale with the issue too
|
|
143
|
+
default = "gpt-6-sol@medium"
|
|
144
|
+
heavy = "gpt-6-sol@high"
|
|
135
145
|
|
|
136
146
|
[agents.opencode]
|
|
137
147
|
vendor = "minimax"
|
|
@@ -139,8 +149,15 @@ roles = ["review"] # here it only reviews; for someone els
|
|
|
139
149
|
[agents.opencode.models.review]
|
|
140
150
|
default = "minimax/MiniMax-M3"
|
|
141
151
|
|
|
152
|
+
[conductor]
|
|
153
|
+
handoff_at = 85 # % da janela → o orq handoff move o conductor para outra conta
|
|
154
|
+
max_per_account = 2 # conta que já tem esse tanto de conductors nunca é alvo do handoff
|
|
155
|
+
reset_grace_min = 15 # toda janela acima do gatilho renova dentro desses minutos → pula a mudança
|
|
156
|
+
|
|
142
157
|
[policy]
|
|
143
|
-
quota_limit = 90 #
|
|
158
|
+
quota_limit = 90 # caps the 5h pick limit; also sets ! in `orq quota --line`
|
|
159
|
+
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)
|
|
160
|
+
# [agents.claude] headroom = 25 # points kept for conductors: children leave the account at limit − 25
|
|
144
161
|
[policy.weights]
|
|
145
162
|
quality = 0.40 # did it deliver? (impl: approved on the first review)
|
|
146
163
|
tokens = 0.45 # did it spend little?
|
|
@@ -150,7 +167,8 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
150
167
|
|
|
151
168
|
### Como o `orq-pick` escolhe
|
|
152
169
|
1. **Filtros:** só agentes com aquele papel, abaixo do limite de quota, não bloqueados (`orq-block`) e, para reviews,
|
|
153
|
-
de um vendor diferente do implementador. Com várias contas,
|
|
170
|
+
de um vendor diferente do implementador. Com várias contas, prefere uma cujo ritmo fique dentro do limite e, como
|
|
171
|
+
desempate, a menos usada.
|
|
154
172
|
2. **Pontuação:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. Os números vêm
|
|
155
173
|
do histórico (`orq-stats`); um agente com poucas execuções usa o `prior` da config e ganha um bônus de exploração.
|
|
156
174
|
3. **Agentes com `reserve = true`** (ex.: Claude, que divide quota com o conductor) só entram quando nada mais serve.
|
|
@@ -159,6 +177,7 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
159
177
|
orq-pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
|
|
160
178
|
orq-stats --days 30 # scoreboard: delivery, approval, time and tokens per agent and model
|
|
161
179
|
orq-block antigravity --days 3 # out of the picks (e.g. weekly quota gone, no collector for it)
|
|
180
|
+
orq pick fix --same .orquestra/<ID>/impl-<repo>.pick # a fix round: the implementer's agent/model/account, effort +1
|
|
162
181
|
```
|
|
163
182
|
|
|
164
183
|
### Idioma e tema
|
|
@@ -187,8 +206,15 @@ quando ele pede uma decisão ou avisa que terminou.
|
|
|
187
206
|
|
|
188
207
|
Para ver tudo de uma vez, deixe o **`orq board`** aberto numa aba ou janela (qualquer terminal, qualquer SO; o
|
|
189
208
|
`herdr-board` o chama): fila de testes, agentes rodando (modelo, memória contra o limite, última ação), conductors e
|
|
190
|
-
suas issues, memória, quota, bloqueios e o placar de 7 dias. `--once` imprime uma
|
|
191
|
-
`ui.toast.delivery = "system"` na config do herdr, cada etapa concluída também gera
|
|
209
|
+
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. Com `ui.toast.delivery = "system"` na config do herdr, cada etapa concluída também gera
|
|
211
|
+
uma notificação nativa do sistema. Para ver a cota sem abrir o board, o `orq quota --line` cabe na barra de abas do
|
|
212
|
+
herdr (uma entrada `command` no `tab_bar_right`, [docs/pt-BR/herdr.md](docs/pt-BR/herdr.md#cota-na-barra-de-abas)).
|
|
213
|
+
O `orq ci` mostra os checks do PR de cada issue reivindicada (na fila, rodando, passou, falhou ou nenhum, lido via
|
|
214
|
+
`gh` na hora — é uma consulta que o usuário pediu agora, então, diferente da coluna de CI do board, não lê só o
|
|
215
|
+
cache); quando um deles vira verde ou vermelho sai uma notificação do herdr, e uma falha também avisa o conductor
|
|
216
|
+
dono num aviso só por commit. Depois dos merges, o `orq gc` lista e remove as worktrees de issues que ficaram para
|
|
217
|
+
trás.
|
|
192
218
|
|
|
193
219
|
Várias issues ao mesmo tempo: um conductor por issue (de preferência em contas diferentes). O `herdr-claim` impede que
|
|
194
220
|
dois conductors peguem a mesma issue. Detalhes, atalhos e cenários: [docs/pt-BR/herdr.md](docs/pt-BR/herdr.md).
|
|
@@ -207,7 +233,7 @@ dois conductors peguem a mesma issue. Detalhes, atalhos e cenários: [docs/pt-BR
|
|
|
207
233
|
|
|
208
234
|
| Comando | O que faz |
|
|
209
235
|
|---|---|
|
|
210
|
-
| `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 (
|
|
236
|
+
| `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) |
|
|
211
237
|
| `orq instructions claude\|agents` | Escreve o playbook do `/issue` como `SKILL.md` (Claude Code) ou dentro do `AGENTS.md` (Codex, OpenCode…) |
|
|
212
238
|
| `orq wait <prefixo>...` | Bloqueia até esses agentes terminarem; imprime `status=` e o arquivo da resposta (qualquer conductor, qualquer terminal) |
|
|
213
239
|
| `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 |
|
|
@@ -216,7 +242,10 @@ dois conductors peguem a mesma issue. Detalhes, atalhos e cenários: [docs/pt-BR
|
|
|
216
242
|
| `herdr-claim` | Dono de cada issue entre os conductors |
|
|
217
243
|
| `herdr-deps` | Liga as dependências do worktree às do repo principal (sem reinstalar) |
|
|
218
244
|
| `orq baseline ...` (`herdr-baseline`) | Snapshot manual dos testes que falham na base (fora do fluxo) |
|
|
219
|
-
| `orq gc [-w <workspace>] [--yes]` | Lista worktrees de issues
|
|
245
|
+
| `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 |
|
|
246
|
+
| `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 |
|
|
247
|
+
| `orq rounds <ID> [-w <workspace>] [--json]` | Watchdog sem LLM: rodadas de fix vs `policy.max_rounds`, tempo por rodada, achados do revisor repetidos entre rodadas, alerta ok / warn / stop. O board marca cada issue (`r2/2 ⚠`) |
|
|
248
|
+
| `orq quota --line` | Uma linha de cota para barra de status (`work 64 · home 94! · codex 81`), do cache do board, nunca da rede |
|
|
220
249
|
| `orq setup` | Primeira configuração e instalação: ambiente, skills do Claude, config, workspace do conductor |
|
|
221
250
|
| `orq completion bash\|zsh\|fish\|powershell` | Imprime o script de autocompletar do shell (veja Instalação) |
|
|
222
251
|
| `orq <config\|set\|lang\|block\|quota>` | O CLI multiplataforma (TypeScript); os nomes `orq-*` e `agent-quota` abaixo chamam ele |
|
package/dist/agents.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { m } from "./i18n.js";
|
|
2
2
|
const NOMCP_CODEX = "-c mcp_servers.plane.enabled=false -c mcp_servers.shadcn.enabled=false";
|
|
3
3
|
const CLAUDE = "claude -p --strict-mcp-config --output-format stream-json --verbose [--model {model}] --effort {effort:low}";
|
|
4
|
+
const CLAUDE_TUI = "claude --strict-mcp-config [--model {model}] --effort {effort:low}";
|
|
4
5
|
const OPENCODE = "opencode run --standalone --format json [-m {model}]"; // v2: effort goes in the model, "provider/model#variant"
|
|
5
6
|
const AGY = "agy --output-format stream-json --sandbox [--model {model}] [--effort {effort}]";
|
|
6
7
|
const COPILOT = "-s -C {dir} --disable-builtin-mcps --disable-mcp-server plane --allow-tool 'shell(git diff:*)' " +
|
|
@@ -14,6 +15,8 @@ export const BUILTIN_AGENTS = {
|
|
|
14
15
|
run: `${CLAUDE} --session-id {session} {mode}`, resume: `${CLAUDE} --resume {session} {mode}`,
|
|
15
16
|
readonly: "--permission-mode plan", write: "--permission-mode auto --add-dir {gitdir}",
|
|
16
17
|
stdin: true, format: "claude", session: "uuid", accounts: true, unset_env: ["HERDR_PANE_ID"],
|
|
18
|
+
tui: `${CLAUDE_TUI} --session-id {session} {mode} --settings {hook_settings} {prompt_text}`,
|
|
19
|
+
tui_resume: `${CLAUDE_TUI} --resume {session} {mode} --settings {hook_settings} {prompt_text}`, turn_hook: "claude",
|
|
17
20
|
},
|
|
18
21
|
codex: {
|
|
19
22
|
// Codex treats any .git inside a writable root as read only: the folders a commit needs are granted one by one.
|
|
@@ -23,12 +26,21 @@ export const BUILTIN_AGENTS = {
|
|
|
23
26
|
resume: `codex exec resume {session} -c model_reasoning_effort={effort:low} ` +
|
|
24
27
|
`-c sandbox_workspace_write.writable_roots={gitroots_json} ${NOMCP_CODEX} -o {out} -`,
|
|
25
28
|
readonly: "", write: "[--add-dir {gitroots}]", stdin: true, format: "codex", answer: "file", throwaway: true,
|
|
29
|
+
// The screen: no approvals (nobody to approve), the same sandbox as exec; notify = end of turn.
|
|
30
|
+
tui: `codex [-m {model}] -c model_reasoning_effort={effort:low} -s workspace-write -a never -C {dir} {mode} ` +
|
|
31
|
+
`${NOMCP_CODEX} -c notify={hook_json} {prompt_text}`,
|
|
32
|
+
tui_resume: `codex resume {session} -c model_reasoning_effort={effort:low} -s workspace-write -a never ` +
|
|
33
|
+
`-c sandbox_workspace_write.writable_roots={gitroots_json} ${NOMCP_CODEX} -c notify={hook_json} {prompt_text}`,
|
|
34
|
+
turn_hook: "codex",
|
|
26
35
|
},
|
|
27
36
|
opencode: {
|
|
28
37
|
// Permissions and MCPs off via OPENCODE_CONFIG_CONTENT. Headless has nobody to approve, so write = --auto (only
|
|
29
38
|
// ever in a worktree) and read only = throwaway worktree with the edit tool denied.
|
|
30
39
|
run: `${OPENCODE} {mode} {prompt_text}`, resume: `${OPENCODE} -s {session} {mode} {prompt_text}`, readonly: "", write: "--auto",
|
|
31
40
|
format: "opencode", throwaway: true, env: { OPENCODE_CONFIG_CONTENT: "{opencode_config}" },
|
|
41
|
+
// The screen: no --auto ("ask" shows up for you); the model and orq's end-of-turn plugin go in the config.
|
|
42
|
+
tui: "opencode --standalone --prompt {prompt_text} {dir}", tui_resume: "opencode --standalone -s {session} --prompt {prompt_text} {dir}",
|
|
43
|
+
turn_hook: "opencode",
|
|
32
44
|
},
|
|
33
45
|
antigravity: {
|
|
34
46
|
// --sandbox + accept-edits (no skip-permissions); headless denies commands outside agy's allowlist, so in practice
|
package/dist/ci.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// CI/CD signal per claimed issue, for `orq ci` and the board's CI column: for each issue with a live owner
|
|
2
|
+
// (.orquestra/<ID>/owner, as orq claim keeps it), the branch its worktree carries (as orq gc matches it:
|
|
3
|
+
// <repo>-plane-<id-lower> under .worktrees) and that branch's PR checks via `gh pr view`, aggregated into
|
|
4
|
+
// queued | running | passed | failed | none. Cached in the state dir (board/ci.json), like quota — orq board
|
|
5
|
+
// never blocks on `gh`; a stale cache starts a background collection the same way quota does. `orq ci` itself is
|
|
6
|
+
// the exception: it is the user explicitly asking for CI status right now, so it runs collectCi() in the
|
|
7
|
+
// foreground (network and all) and prints the result — only `orq board` reads the cache.
|
|
8
|
+
// On a transition to passed or failed: a herdr desktop notification; on failed, one line to the owning
|
|
9
|
+
// conductor via testrun.ts's notify(), tracked by `${state}:${head SHA}` in the cache so a repeat collection of
|
|
10
|
+
// the same commit never repeats it, but a new commit that is still red does.
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { aliveOwner } from "./commands/claim.js";
|
|
16
|
+
import { writeAtomic } from "./fsutil.js";
|
|
17
|
+
import { m } from "./i18n.js";
|
|
18
|
+
import { alive } from "./lock.js";
|
|
19
|
+
import { stateDir } from "./paths.js";
|
|
20
|
+
import { notify } from "./testrun.js";
|
|
21
|
+
const git = (cwd, ...args) => spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8" });
|
|
22
|
+
const output = (cwd, ...args) => { const r = git(cwd, ...args); return r.status === 0 ? r.stdout.trim() : undefined; };
|
|
23
|
+
// ORQ_GH may be a .js file (tests: a fake gh that also runs on Windows), as orq gc already does it.
|
|
24
|
+
const gh = (cwd, args) => {
|
|
25
|
+
const bin = process.env.ORQ_GH || "gh";
|
|
26
|
+
return bin.endsWith(".js") ? spawnSync(process.execPath, [bin, ...args], { cwd, encoding: "utf8", timeout: 15_000 })
|
|
27
|
+
: spawnSync(bin, args, { cwd, encoding: "utf8", timeout: 15_000 });
|
|
28
|
+
};
|
|
29
|
+
export const cacheFile = () => join(stateDir(), "board", "ci.json");
|
|
30
|
+
export function loadCache() {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(cacheFile(), "utf8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function saveCache(entries) { writeAtomic(cacheFile(), JSON.stringify(entries, null, 1)); }
|
|
39
|
+
// A per-workspace "collected at" marker, separate from ci.json: collectCi() writes no cache entry for a workspace
|
|
40
|
+
// with no claimed issue that has a matching worktree, so ci.json alone can't tell "never collected" apart from
|
|
41
|
+
// "collected, found nothing" — the board would otherwise relaunch the collector every redraw forever in the
|
|
42
|
+
// latter case. Keyed by a hash of the workspace path since it doubles as a file name.
|
|
43
|
+
const wsKey = (ws) => createHash("sha1").update(ws).digest("hex").slice(0, 16);
|
|
44
|
+
const collectedFile = (ws) => join(stateDir(), "board", "ci", `${wsKey(ws)}.at`);
|
|
45
|
+
export function markCollected(ws, at) { writeAtomic(collectedFile(ws), String(at)); }
|
|
46
|
+
/** When `ws` was last collected (collectCi ran for it, even if it found nothing), else undefined if never. */
|
|
47
|
+
export function collectedAt(ws) {
|
|
48
|
+
try {
|
|
49
|
+
const n = Number(readFileSync(collectedFile(ws), "utf8"));
|
|
50
|
+
return Number.isFinite(n) ? n : undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Claimed issues of a workspace with a live owner (as `orq claim -l` lists them), issue id → owner pane + kind
|
|
57
|
+
* ("herdr" for a pane herdr-notify can reach, else a plain terminal conductor — see handoff.ts's same check). */
|
|
58
|
+
export function claimedIssues(ws) {
|
|
59
|
+
const O = join(ws, ".orquestra"), out = [];
|
|
60
|
+
for (const d of existsSync(O) ? readdirSync(O).filter((x) => !x.startsWith(".")) : []) {
|
|
61
|
+
const f = join(O, d, "owner");
|
|
62
|
+
if (!existsSync(f))
|
|
63
|
+
continue;
|
|
64
|
+
const t = readFileSync(f, "utf8").trim();
|
|
65
|
+
if (aliveOwner(t))
|
|
66
|
+
out.push({ id: d, owner: /pane=(\S+)/.exec(t)?.[1] ?? "", kind: /kind=(\S+)/.exec(t)?.[1] ?? "herdr" });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/** Every worktree branch in the workspace's repos, one `git worktree list` per repo — called once per workspace
|
|
71
|
+
* per collection (collectCi memoizes it across issues) rather than once per claimed issue. */
|
|
72
|
+
export function listWorktrees(ws) {
|
|
73
|
+
if (!existsSync(join(ws, ".worktrees")))
|
|
74
|
+
return [];
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const repoName of readdirSync(ws, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)) {
|
|
77
|
+
const repo = join(ws, repoName);
|
|
78
|
+
if (!existsSync(join(repo, ".git")))
|
|
79
|
+
continue;
|
|
80
|
+
const listings = output(repo, "worktree", "list", "--porcelain");
|
|
81
|
+
if (!listings)
|
|
82
|
+
continue;
|
|
83
|
+
for (const block of listings.split(/\n\n/)) {
|
|
84
|
+
const rec = Object.fromEntries(block.split("\n").map((line) => {
|
|
85
|
+
const i = line.indexOf(" ");
|
|
86
|
+
return [i < 0 ? line : line.slice(0, i), i < 0 ? "" : line.slice(i + 1)];
|
|
87
|
+
}));
|
|
88
|
+
const branch = rec.branch?.replace(/^refs\/heads\//, "") ?? "";
|
|
89
|
+
if (branch)
|
|
90
|
+
out.push({ repo: repoName, branch });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** The repo + branch an issue's worktree carries, the same match `orq gc` uses: a worktree branch ending in
|
|
96
|
+
* "plane-<id, lowercase>" under <ws>/.worktrees. An issue can have one per repo (several repos in play).
|
|
97
|
+
* `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
|
+
const suffix = `plane-${issue.toLowerCase()}`;
|
|
100
|
+
return all.filter((b) => b.branch.toLowerCase().endsWith(suffix));
|
|
101
|
+
}
|
|
102
|
+
// GitHub's statusCheckRollup mixes CheckRun (status/conclusion, the GitHub Actions / Checks API shape) with the
|
|
103
|
+
// legacy commit-status shape (StatusContext: just `state`, no status/conclusion at all) for repos whose CI still
|
|
104
|
+
// posts plain commit statuses (Jenkins, CircleCI, old GitLab integrations). Left unmapped, every StatusContext
|
|
105
|
+
// item reads as still-running forever: conclusion is undefined (never "failed"), status is undefined (never
|
|
106
|
+
// "queued"), and `status !== "COMPLETED"` is always true.
|
|
107
|
+
const STATUS_STATE = {
|
|
108
|
+
SUCCESS: { status: "COMPLETED", conclusion: "SUCCESS" }, FAILURE: { status: "COMPLETED", conclusion: "FAILURE" },
|
|
109
|
+
ERROR: { status: "COMPLETED", conclusion: "ERROR" }, PENDING: { status: "PENDING" },
|
|
110
|
+
};
|
|
111
|
+
const normalize = (c) => c.__typename === "StatusContext" ? (STATUS_STATE[c.state ?? ""] ?? { status: c.status, conclusion: c.conclusion }) : c;
|
|
112
|
+
/** The worst signal across a PR's checks: any failure beats a pending one, which beats still running, which beats
|
|
113
|
+
* all green; no checks at all (none configured, or not registered yet) reads as "none", not a false "passed". */
|
|
114
|
+
export function aggregateChecks(rollup) {
|
|
115
|
+
if (!rollup.length)
|
|
116
|
+
return "none";
|
|
117
|
+
const checks = rollup.map(normalize);
|
|
118
|
+
if (checks.some((c) => ["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "CANCELLED", "ERROR"].includes(c.conclusion ?? "")))
|
|
119
|
+
return "failed";
|
|
120
|
+
if (checks.some((c) => c.status === "QUEUED" || c.status === "PENDING" || c.status === "WAITING" || c.status === "REQUESTED"))
|
|
121
|
+
return "queued";
|
|
122
|
+
if (checks.some((c) => c.status !== "COMPLETED"))
|
|
123
|
+
return "running";
|
|
124
|
+
return "passed";
|
|
125
|
+
}
|
|
126
|
+
/** `gh pr view --json number,headRefOid,statusCheckRollup`'s stdout → the PR's number, head SHA and CI state;
|
|
127
|
+
* no PR (empty/garbage JSON, or no "number") reads as "none". Pure: no network, so it needs no fake gh to test. */
|
|
128
|
+
export function parsePrStatus(json) {
|
|
129
|
+
try {
|
|
130
|
+
const j = JSON.parse(json);
|
|
131
|
+
if (!j.number)
|
|
132
|
+
return { state: "none" };
|
|
133
|
+
return { pr: j.number, sha: j.headRefOid, state: aggregateChecks(j.statusCheckRollup ?? []) };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { state: "none" };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** The branch's PR and CI state; no `gh`, no PR, or a `gh` failure all read as "none" — never an error. */
|
|
140
|
+
function prStatus(repo, branch) {
|
|
141
|
+
const r = gh(repo, ["pr", "view", branch, "--json", "number,headRefOid,statusCheckRollup"]);
|
|
142
|
+
return r.status === 0 ? parsePrStatus(r.stdout) : { state: "none" };
|
|
143
|
+
}
|
|
144
|
+
// `orq ci -w ws2` (foreground) and the board's detached `orq board --collect-ci` for ws1 can run at the same
|
|
145
|
+
// time; both do a read-modify-write of the single shared ci.json, so without a lock the last one to save erases
|
|
146
|
+
// the other's entries for its own workspace (and can drop a `notified` mark, causing a repeat notification).
|
|
147
|
+
// A short-lived exclusive lock file around just the read-merge-write (never around the `gh`/network calls above
|
|
148
|
+
// it) keeps that atomic; a stale lock (holder process died mid-write) is detected via lock.ts's `alive()` and
|
|
149
|
+
// cleared instead of being waited on forever.
|
|
150
|
+
function withCacheLock(fn) {
|
|
151
|
+
const dir = join(stateDir(), "board"), lf = join(dir, "ci.lock");
|
|
152
|
+
mkdirSync(dir, { recursive: true });
|
|
153
|
+
for (;;) {
|
|
154
|
+
try {
|
|
155
|
+
writeFileSync(lf, String(process.pid), { flag: "wx" });
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
if (e.code !== "EEXIST")
|
|
160
|
+
throw e;
|
|
161
|
+
try {
|
|
162
|
+
if (!alive(Number(readFileSync(lf, "utf8").trim() || "0"))) {
|
|
163
|
+
rmSync(lf, { force: true });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
continue;
|
|
169
|
+
} // lock file vanished mid-check: retry the exclusive create
|
|
170
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
return fn();
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
rmSync(lf, { force: true });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function notifyDesktop(issue, repo, state) {
|
|
181
|
+
const title = state === "passed" ? m("CI passed", "CI passou") : m("CI failed", "CI falhou");
|
|
182
|
+
spawnSync("herdr", ["notification", "show", title, "--body", `${issue} ${repo}`], { stdio: "ignore" });
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* This workspace's current CI entries, merged into the cache (other workspaces' entries untouched). On a genuine
|
|
186
|
+
* transition (a known previous sighting, now a different head commit) to passed or failed: a herdr desktop
|
|
187
|
+
* notification, and on failed one line to the owning conductor — gated by `${state}:${sha}` in `notified`, so a
|
|
188
|
+
* repeat collection of the same head commit never notifies twice, but a new commit that is still red does (the
|
|
189
|
+
* mark carries the sha, so it is never redundant with the `prev` gate that skips a first sighting).
|
|
190
|
+
* The `prev` read, the notify decision and the `notified` mark all happen inside `withCacheLock`, alongside the
|
|
191
|
+
* save — otherwise a concurrent collector of the same workspace (`orq ci` in the foreground racing the board's
|
|
192
|
+
* detached collector) reads the same stale `prev`, both decide to notify and whichever saves last overwrites the
|
|
193
|
+
* other's freshly written `notified` mark, causing a third notification on the next collection.
|
|
194
|
+
* The notifications themselves fire after the lock is released: they must never hold up another collector's
|
|
195
|
+
* read-modify-write while they run.
|
|
196
|
+
* A non-herdr owner (a plain terminal conductor, same check as handoff.ts) has no pane herdr-notify can reach, so
|
|
197
|
+
* the line goes through `hooks.notice` (console.log by default) instead — never silently dropped, and never a
|
|
198
|
+
* crash from `herdr agent get` failing. The owner notify is always detached (background=true): collectCi() must
|
|
199
|
+
* never block on a conductor stuck in a dialog.
|
|
200
|
+
* `hooks` lets tests swap the notifiers for ones with no side effects, lets `orq ci --json` keep its stdout to
|
|
201
|
+
* just the JSON, and lets the board's detached collector (stdio: "ignore") persist `notice` to a file instead of
|
|
202
|
+
* the default console.log, which would otherwise vanish.
|
|
203
|
+
*/
|
|
204
|
+
export function collectCi(ws, hooks = {}) {
|
|
205
|
+
// Detached: collectCi() must never block on a conductor stuck in a dialog (herdr-notify waits up to 30 min).
|
|
206
|
+
const desktop = hooks.desktop ?? notifyDesktop, toOwner = hooks.owner ?? ((pane, line) => notify(pane, line, true)), 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) })));
|
|
209
|
+
const toNotify = [];
|
|
210
|
+
const mine = withCacheLock(() => {
|
|
211
|
+
const cache = loadCache(), mine = [];
|
|
212
|
+
for (const { id, owner, kind, repo, branch, pr, sha, state } of found) {
|
|
213
|
+
const prev = cache.find((e) => e.ws === ws && e.issue === id && e.repo === repo && e.branch === branch);
|
|
214
|
+
let notified = prev?.notified;
|
|
215
|
+
const mark = sha ? `${state}:${sha}` : undefined;
|
|
216
|
+
if (prev && (state === "passed" || state === "failed") && mark && notified !== mark) {
|
|
217
|
+
toNotify.push({ owner, kind, id, repo, state, sha: sha });
|
|
218
|
+
notified = mark;
|
|
219
|
+
}
|
|
220
|
+
if (!prev && mark)
|
|
221
|
+
notified = mark; // first sighting: known, not news
|
|
222
|
+
mine.push({ ws, issue: id, repo, branch, ...(pr !== undefined ? { pr } : {}), ...(sha ? { sha } : {}), state, ...(notified ? { notified } : {}), at: now });
|
|
223
|
+
}
|
|
224
|
+
saveCache([...cache.filter((e) => e.ws !== ws), ...mine]);
|
|
225
|
+
return mine;
|
|
226
|
+
});
|
|
227
|
+
for (const { owner, kind, id, repo, state, sha } of toNotify) {
|
|
228
|
+
desktop(id, repo, state);
|
|
229
|
+
if (state === "failed" && owner) {
|
|
230
|
+
const line = `[ORQ CI] ${id} ${repo} failed (${sha.slice(0, 7)})`;
|
|
231
|
+
if (kind === "herdr")
|
|
232
|
+
toOwner(owner, line);
|
|
233
|
+
else
|
|
234
|
+
notice(line);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
markCollected(ws, now);
|
|
238
|
+
return mine;
|
|
239
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -9,22 +9,25 @@ import { board, USAGE as BOARD } from "./commands/board.js";
|
|
|
9
9
|
import { baseline, USAGE as BASELINE } from "./commands/baseline.js";
|
|
10
10
|
import { block, USAGE as BLOCK } from "./commands/block.js";
|
|
11
11
|
import { claim, USAGE as CLAIM } from "./commands/claim.js";
|
|
12
|
+
import { ci, USAGE as CI } from "./commands/ci.js";
|
|
12
13
|
import { completion, USAGE as COMPLETION } from "./commands/completion.js";
|
|
13
14
|
import { config, USAGE as CONFIG } from "./commands/config.js";
|
|
14
15
|
import { deps, USAGE as DEPS } from "./commands/deps.js";
|
|
15
16
|
import { gc, USAGE as GC } from "./commands/gc.js";
|
|
17
|
+
import { handoff, USAGE as HANDOFF } from "./commands/handoff.js";
|
|
16
18
|
import { instructions, USAGE as INSTRUCTIONS } from "./commands/instructions.js";
|
|
17
19
|
import { lang, USAGE as LANG } from "./commands/lang.js";
|
|
18
20
|
import { lock, USAGE as LOCK } from "./commands/lock.js";
|
|
19
21
|
import { pick, USAGE as PICK } from "./commands/pick.js";
|
|
20
22
|
import { quota, USAGE as QUOTA } from "./commands/quota.js";
|
|
23
|
+
import { rounds, USAGE as ROUNDS } from "./commands/rounds.js";
|
|
21
24
|
import { run, USAGE as RUN } from "./commands/run.js";
|
|
22
25
|
import { set, USAGE as SET } from "./commands/set.js";
|
|
23
26
|
import { setup, USAGE as SETUP } from "./commands/setup.js";
|
|
24
27
|
import { stats, USAGE as STATS } from "./commands/stats.js";
|
|
25
28
|
import { verify, USAGE as VERIFY } from "./commands/verify.js";
|
|
26
29
|
import { wait, USAGE as WAIT } from "./commands/wait.js";
|
|
27
|
-
const COMMANDS = { config, set, board, lang, block, quota, pick, stats, setup, lock, claim, deps, gc, run, agent, wait, instructions, verify, baseline,
|
|
30
|
+
const COMMANDS = { config, set, board, lang, block, quota, pick, stats, rounds, setup, lock, claim, deps, gc, ci, handoff, run, agent, wait, instructions, verify, baseline,
|
|
28
31
|
completion: (a) => completion(a, Object.keys(COMMANDS)) };
|
|
29
32
|
const usage = () => `orq <command> [args]
|
|
30
33
|
|
|
@@ -35,17 +38,20 @@ ${LANG}
|
|
|
35
38
|
${QUOTA}
|
|
36
39
|
${PICK}
|
|
37
40
|
${STATS}
|
|
41
|
+
${ROUNDS}
|
|
38
42
|
${BOARD}
|
|
39
43
|
${LOCK}
|
|
40
44
|
${RUN}
|
|
41
45
|
${AGENT}
|
|
42
46
|
${WAIT}
|
|
47
|
+
${HANDOFF}
|
|
43
48
|
${VERIFY}
|
|
44
49
|
${BASELINE}
|
|
45
50
|
${INSTRUCTIONS}
|
|
46
51
|
${CLAIM}
|
|
47
52
|
${DEPS}
|
|
48
53
|
${GC}
|
|
54
|
+
${CI}
|
|
49
55
|
${COMPLETION}
|
|
50
56
|
${BLOCK.split("\n")[0]}
|
|
51
57
|
(orq block --help ${m("for more", "para mais")})`;
|