@gevezex/gdt 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +375 -0
  3. package/dist/agents/adapter.js +7 -0
  4. package/dist/agents/claude.js +13 -0
  5. package/dist/agents/codex.js +13 -0
  6. package/dist/agents/index.js +15 -0
  7. package/dist/agents/mcode.js +14 -0
  8. package/dist/agents/omp.js +14 -0
  9. package/dist/agents/opencode.js +15 -0
  10. package/dist/agents/pi.js +14 -0
  11. package/dist/backends/backend.js +1 -0
  12. package/dist/backends/headless.js +55 -0
  13. package/dist/backends/herdr.js +286 -0
  14. package/dist/backends/index.js +20 -0
  15. package/dist/cli.js +466 -0
  16. package/dist/config.js +210 -0
  17. package/dist/contract.js +133 -0
  18. package/dist/decision.js +175 -0
  19. package/dist/doctor.js +231 -0
  20. package/dist/finding.js +3 -0
  21. package/dist/git.js +25 -0
  22. package/dist/github.js +95 -0
  23. package/dist/locale.js +44 -0
  24. package/dist/notify.js +29 -0
  25. package/dist/prompts.js +80 -0
  26. package/dist/protocol.js +114 -0
  27. package/dist/state.js +113 -0
  28. package/dist/steering.js +165 -0
  29. package/dist/supervisor.js +369 -0
  30. package/dist/worker.js +232 -0
  31. package/dist/workflow.js +333 -0
  32. package/locales/en.toml +28 -0
  33. package/locales/nl.toml +28 -0
  34. package/package.json +61 -0
  35. package/roles/developer.md +66 -0
  36. package/roles/issue-writer.md +40 -0
  37. package/roles/reviewer.md +65 -0
  38. package/roles/tester.md +58 -0
  39. package/schemas/gdt-answer.v1.schema.json +32 -0
  40. package/schemas/gdt-directive.v1.schema.json +36 -0
  41. package/schemas/gdt-handoff.v1.schema.json +117 -0
  42. package/schemas/gdt-question.v1.schema.json +82 -0
  43. package/schemas/gdt-review.v1.schema.json +131 -0
  44. package/schemas/gdt-round.v1.schema.json +28 -0
  45. package/schemas/gdt-test.v1.schema.json +131 -0
  46. package/skill/SKILL.md +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayhan Cicek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # gdt
2
+
3
+ **GitHub issues to merge-ready pull requests, with a developer, tester and reviewer agent.**
4
+
5
+ > Status: early development (0.x), used daily on this repository (see
6
+ > [docs/dogfooding.md](docs/dogfooding.md)). Background:
7
+ > [docs/design.md](docs/design.md).
8
+
9
+ gdt takes one GitHub issue and runs it through three independent agent roles
10
+ until a single draft pull request is ready to merge, or until it needs your
11
+ decision:
12
+
13
+ - the **developer** implements the issue and opens a draft pull request;
14
+ - the **tester** checks every acceptance criterion against the running code;
15
+ - the **reviewer** reads the diff against the issue.
16
+
17
+ A deterministic **supervisor** (plain code, no model) decides whose turn it is.
18
+ Waiting, polling and deciding cost no model tokens; a model only runs during a
19
+ role's turn. You drive gdt by talking to any coding agent (Claude Code, Codex,
20
+ OpenCode, MCode, pi or omp) and can watch the roles work in
21
+ [herdr](https://herdr.dev). **You always merge yourself**: gdt never merges,
22
+ deploys or closes issues.
23
+
24
+ ## How it works
25
+
26
+ ### The big picture
27
+
28
+ ```text
29
+ ┌──────────┐ "pick up issue 251 ┌──────────────────────┐
30
+ │ you │ ──────────────────────► │ operator agent │ Claude Code, Codex,
31
+ └──────────┘ with gdt" │ (your chat session) │ OpenCode, ...
32
+ ▲ └──────────┬───────────┘
33
+ │ │ gdt start 251 (returns at once)
34
+ │ │ gdt wait 251 (background, 0 tokens)
35
+ │ ▼
36
+ │ ┌──────────────────────────────────────────┐
37
+ │ │ supervisor (deterministic, no model) │
38
+ │ │ reads GitHub every poll_seconds, │
39
+ │ │ decides the next turn, enforces gates │
40
+ │ └───────┬──────────────┬──────────────┬────┘
41
+ │ dispatch│ dispatch│ dispatch│
42
+ │ ▼ ▼ ▼
43
+ │ ┌────────────┐ ┌────────────┐ ┌────────────┐
44
+ │ │ developer │ │ tester │ │ reviewer │
45
+ │ │ worker │ │ worker │ │ worker │
46
+ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
47
+ │ │ one turn │ one turn │ one turn
48
+ │ ▼ ▼ ▼
49
+ │ ┌──────────────────────────────────────────┐
50
+ │ │ GitHub: issue #251 + one draft PR │
51
+ │ │ code, commits and [gdt-*:v1] records │
52
+ │ └──────────────────────────────────────────┘
53
+ │
54
+ └──── notification + `gdt wait` returns on:
55
+ awaiting_human │ blocked │ failed │ ready_to_merge
56
+ ```
57
+
58
+ - The **operator** is the agent you already chat with. It only runs `gdt`
59
+ commands and relays your words; it never writes code in the workflow.
60
+ - The **supervisor** runs as its own process (a herdr pane or a detached
61
+ process), so closing or compacting your chat never stops a workflow.
62
+ - Each **worker** waits for a dispatch without using a model, runs exactly one
63
+ agent turn (for example `claude -p ...`), and goes back to waiting.
64
+ - Roles talk to each other only through **records** posted as comments on
65
+ GitHub (`[gdt-handoff:v1]`, `[gdt-test:v1]`, `[gdt-review:v1]`, ...). The
66
+ supervisor reads those records; nothing is passed by chat.
67
+
68
+ ### One issue, start to finish
69
+
70
+ ```text
71
+ write the issue ──► gdt check-issue 251 ──► invalid: fix the body, check again
72
+ │ valid
73
+ ▼
74
+ ┌──────────────────────────────────────────────────────────┐
75
+ │ gdt start 251 │
76
+ │ preflight: config valid, clean working tree, contract ok │
77
+ └────────┬─────────────────────────────────────────────────┘
78
+ ▼
79
+ ┌──────────────────────────────────────────────────────────┐
80
+ │ developer (round 0): branch, implement the ACs, tests, │◄──────┐
81
+ │ draft PR with "Closes #251", post [gdt-handoff:v1] │ │
82
+ └────────┬─────────────────────────────────────────────────┘ │
83
+ ▼ │
84
+ ┌──────────────────────┐ │
85
+ │ tester (read-only) │ checks every AC on the PR head │
86
+ │ post [gdt-test:v1] ├── changes_requested ──┐ │
87
+ └────────┬─────────────┘ │ │
88
+ │ approved ▼ │
89
+ ┌──────────────────────┐ ┌─────────────────────┐ yes │
90
+ │ reviewer (read-only) │ │ correction rounds ├────────┘
91
+ │ post [gdt-review:v1] ├── changes ►│ left? │ developer fixes
92
+ └────────┬─────────────┘ requested └─────────┬───────────┘ (round 1, 2, ...)
93
+ │ approved │ no
94
+ ▼ ▼
95
+ ┌─────────────────────────────┐ ┌─────────────────────┐
96
+ │ gates │ │ blocked │
97
+ │ - every AC passed by both │ │ you decide: │
98
+ │ - no blocking findings │ │ gdt allow-round 251 │
99
+ │ - no merge conflict │ └─────────────────────┘
100
+ │ - required CI checks green │
101
+ └────────┬────────────────────┘
102
+ │ all pass
103
+ ▼
104
+ ┌─────────────────────────────┐
105
+ │ ready_to_merge ├──► you review and merge the PR
106
+ └─────────────────────────────┘ ("Closes #251" closes the issue)
107
+
108
+ At any point a role may post [gdt-question:v1] → status awaiting_human
109
+ → you answer through the operator (gdt answer 251 Q1 "...") → that role resumes.
110
+ ```
111
+
112
+ Evidence is bound to the exact pull request head: when the developer pushes a
113
+ new commit, earlier test and review results no longer count and the tester and
114
+ reviewer run again. Editing the issue body (with a Changelog entry) resets all
115
+ evidence too.
116
+
117
+ ## Requirements
118
+
119
+ | Tool | Why |
120
+ |---|---|
121
+ | Node 24 LTS | runs gdt |
122
+ | `git` | the shared checkout the roles work in |
123
+ | `gh`, logged in (`gh auth login`) | issues, pull requests, comments, checks |
124
+ | at least one agent CLI | `claude`, `codex`, `opencode`, `mcode`, `pi` or `omp` |
125
+ | [herdr](https://herdr.dev) 0.9.1+ (optional) | watch the roles live; otherwise use `terminal = "headless"` |
126
+
127
+ ## Install
128
+
129
+ ```bash
130
+ npm i -g @gevezex/gdt
131
+ gdt --version
132
+ ```
133
+
134
+ Or run it once without installing: `npx @gevezex/gdt doctor`.
135
+
136
+ From source instead:
137
+
138
+ ```bash
139
+ git clone https://github.com/gevezex/gdt.git
140
+ cd gdt
141
+ npm ci
142
+ npm run build
143
+ npm link # puts `gdt` on your PATH
144
+ ```
145
+
146
+ Then install the operator skill, so your coding agent knows how to drive gdt:
147
+
148
+ ```bash
149
+ gdt install-skill
150
+ ```
151
+
152
+ It copies `skill/SKILL.md` into the skill directory of every agent CLI it finds
153
+ (for example `~/.claude/skills/gdt`) and is safe to run again.
154
+
155
+ ## Quick start
156
+
157
+ ### 1. Configure the target repository
158
+
159
+ In the repository you want gdt to work on, commit `.gdt/config.toml`:
160
+
161
+ ```toml
162
+ language = "en" # language for issue and PR text: "en" or "nl"
163
+
164
+ [roles.developer]
165
+ agent = "opencode"
166
+ model = "deepseek/deepseek-v4-flash"
167
+
168
+ [roles.tester]
169
+ agent = "claude"
170
+ model = "claude-sonnet-5"
171
+
172
+ [roles.reviewer]
173
+ agent = "codex"
174
+ model = "gpt-5.6-luna"
175
+
176
+ [workflow]
177
+ required_checks = ["test"] # CI check names that must be green
178
+ terminal = "herdr" # or "headless"
179
+ ```
180
+
181
+ Then check your setup:
182
+
183
+ ```bash
184
+ gdt doctor
185
+ ```
186
+
187
+ `doctor` checks `git`, `gh` and its login, the agent CLIs, herdr (when used)
188
+ and the config, and prints a `fix:` line for every problem. It also warns when
189
+ developer and tester use the same model vendor, because the tester is less
190
+ independent then.
191
+
192
+ ### 2. Write the issue as a contract
193
+
194
+ The issue body is the only specification. It needs fixed sections and numbered
195
+ acceptance criteria, each with Given, When, Then and a concrete Example:
196
+
197
+ ```markdown
198
+ ## Plain language
199
+ One paragraph for humans.
200
+
201
+ ## Goal
202
+ ## Context
203
+ ## Definitions
204
+
205
+ ## Acceptance criteria
206
+
207
+ **AC-1: Short title**
208
+
209
+ - Given: the starting situation
210
+ - When: the action
211
+ - Then: the observable result
212
+ - Example: `gdt foo 12` prints `bar`
213
+
214
+ ## Non-functional
215
+ ## Out of scope
216
+ ## Assumptions
217
+
218
+ ## Open questions
219
+
220
+ None.
221
+
222
+ ## Changelog
223
+ - 2026-09-27: first version
224
+
225
+ ## Readiness
226
+ ```
227
+
228
+ Work starts only when `Open questions` is exactly `None.` Vague phrases such as
229
+ "robust" or "etc." are rejected. Headings come from
230
+ [`locales/<language>.toml`](locales), so a Dutch issue uses `Acceptatiecriteria`,
231
+ `Gegeven`, `Wanneer` and so on. Check an issue before starting:
232
+
233
+ ```bash
234
+ gdt check-issue 251 # an existing issue
235
+ gdt check-issue --body-file body.md # a draft, without calling GitHub
236
+ ```
237
+
238
+ Your agent can help write the body with the issue-writer instructions in
239
+ [`roles/issue-writer.md`](roles/issue-writer.md).
240
+
241
+ ### 3. Start it from your agent
242
+
243
+ Just ask your coding agent, in your own language:
244
+
245
+ > pick up issue 251 with gdt
246
+
247
+ The agent runs `gdt start 251` and then `gdt wait 251` in the background. You
248
+ get a desktop notification when something needs you. Come back to the same
249
+ chat and ask "what's going on?".
250
+
251
+ You can also run it by hand:
252
+
253
+ ```bash
254
+ gdt start 251 # starts the supervisor and workers, returns immediately
255
+ gdt wait 251 # blocks until the workflow needs attention
256
+ gdt status 251 # one line plus the next step
257
+ ```
258
+
259
+ ### 4. Answer, steer, merge
260
+
261
+ | Situation | What you (or your agent) run |
262
+ |---|---|
263
+ | A role asks a question | `gdt answer 251 Q1 "use the existing config file"` |
264
+ | You want to guide one role | `gdt steer 251 --role developer "keep the public API unchanged"` |
265
+ | Round budget used up | `gdt allow-round 251` |
266
+ | A turn failed or crashed | `gdt retry 251`, then `gdt start 251` |
267
+ | Try another model for a role | `gdt set-agent 251 tester claude/claude-opus-5-5` |
268
+ | `ready_to_merge` | review the draft PR, mark it ready and merge it yourself |
269
+
270
+ The operator only posts answers and directives in your words, or after you
271
+ confirmed the text. Directives are guidance, not contract: a directive that
272
+ changes product behaviour makes the role ask for the issue body to be updated.
273
+
274
+ ## Workflow statuses
275
+
276
+ `gdt status <n>` always prints the status and the next step.
277
+
278
+ | Status | Meaning | Next step |
279
+ |---|---|---|
280
+ | `starting` / `running` | a role turn is running | wait |
281
+ | `waiting_for_checks` | approved; CI or mergeability pending | wait |
282
+ | `awaiting_human` | a role asked a question | `gdt answer <n> <question-id> "<text>"` |
283
+ | `blocked` | a gate failed or a role reported blocked; the reason says why | follow the hint, e.g. `gdt allow-round <n>` |
284
+ | `contract_changed` | the issue body changed; evidence is reset | wait |
285
+ | `failed` | an agent turn exited non-zero | `gdt retry <n>` |
286
+ | `paused` / `stopped` | you paused or stopped it | `gdt resume <n>` / `gdt start <n>` |
287
+ | `ready_to_merge` | all gates passed | review and merge the PR |
288
+
289
+ ## Commands
290
+
291
+ Every command supports `--help`; `status` and `wait` also support `--json`.
292
+
293
+ | Command | Effect |
294
+ |---|---|
295
+ | `gdt doctor` | Check tools, GitHub login, agents, herdr and `.gdt/config.toml` |
296
+ | `gdt check-issue <n>` | Validate an issue body against the contract |
297
+ | `gdt start <n>` | Preflight, start the supervisor and workers, return |
298
+ | `gdt status <n>` | Status, role, round, open findings and next step |
299
+ | `gdt wait <n>` | Block (without tokens) until the workflow needs attention |
300
+ | `gdt stop <n>` | Stop everything; `gdt start` resumes the same workflow |
301
+ | `gdt pause <n>` / `gdt resume <n>` | Stop / continue dispatching new turns |
302
+ | `gdt answer <n> <id> <text>` | Answer an open question |
303
+ | `gdt steer <n> --role <role> <text>` | Send a directive to one role's next turn |
304
+ | `gdt allow-round <n>` | Grant one extra correction round |
305
+ | `gdt retry <n>` | Clear a failed or interrupted turn so it runs again |
306
+ | `gdt set-agent <n> <role> <agent>/<model>` | Override one role's agent for this issue |
307
+ | `gdt install-skill` | Install the operator skill into your agent CLIs |
308
+
309
+ ## Configuration
310
+
311
+ `.gdt/config.toml` is committed; `.gdt/config.local.toml` holds machine-local
312
+ overrides (for example another model) and is not committed.
313
+
314
+ | Key | Default | Meaning |
315
+ |---|---|---|
316
+ | `language` | `"en"` | Language of issue and PR text (`en`, `nl`) |
317
+ | `roles.<role>.agent` | required | `claude`, `codex`, `opencode`, `mcode`, `pi` or `omp` |
318
+ | `roles.<role>.model` | required | Model id for that agent |
319
+ | `workflow.required_checks` | required | CI checks that must be green before `ready_to_merge` |
320
+ | `workflow.allow_no_required_checks` | `false` | Allow an empty `required_checks` list |
321
+ | `workflow.max_correction_rounds` | `2` | Correction rounds after round 0 |
322
+ | `workflow.terminal` | `"herdr"` | `"herdr"` or `"headless"` |
323
+ | `workflow.supervisor_pane` | `false` | herdr: also show the supervisor in a pane |
324
+ | `workflow.poll_seconds` | `30` | How often the supervisor reads GitHub |
325
+ | `contract.max_acceptance_criteria` | `8` | Maximum number of ACs per issue |
326
+ | `contract.extra_rules` | none | File with project rules added to every role prompt |
327
+
328
+ How each agent CLI is invoked is documented in [docs/agents.md](docs/agents.md).
329
+
330
+ ## Watching it: herdr or headless
331
+
332
+ ```text
333
+ herdr workspace "gdt-251" headless
334
+ ┌──────────────┬──────────────┬──────────────┐
335
+ │ developer · │ tester · │ reviewer · │ detached processes,
336
+ │ opencode · │ claude · │ codex · │ one log file each in
337
+ │ RUNNING │ WAITING │ WAITING │ .git/gdt/issue-251/logs/
338
+ └──────────────┴──────────────┴──────────────┘
339
+ supervisor runs detached → logs/supervisor.log
340
+ ```
341
+
342
+ Everything gdt keeps for an issue lives under `.git/gdt/issue-<n>/`: `state.json`
343
+ (the workflow state), `logs/` (one log per process) and `runs/` (the prompt and
344
+ result of every turn). It is never committed.
345
+
346
+ ## Principles
347
+
348
+ - **The issue body is the contract.** Work starts only when there are no open questions.
349
+ - **Independent verification.** The tester derives its checks from the contract,
350
+ not from the developer's claims. Evidence is bound to the exact PR head.
351
+ - **Bounded cost.** A fixed correction-round budget; models run only on dispatched turns.
352
+ - **Fail closed.** When a trustworthy decision is impossible, gdt stops and tells you why.
353
+ - **Role boundaries are checked.** After a tester or reviewer turn, the supervisor
354
+ verifies that HEAD, branch and tracked files are unchanged.
355
+ - **Your language on GitHub.** Issues and PR text in your configured language;
356
+ protocol, code and CLI output in English.
357
+ - **Humans merge.** gdt never merges, deploys or closes issues.
358
+
359
+ ## Development
360
+
361
+ Requires Node 24 LTS.
362
+
363
+ ```bash
364
+ npm ci
365
+ npm run build
366
+ npm run lint
367
+ npm test
368
+ node dist/cli.js doctor # run inside a repository with .gdt/config.toml
369
+ ```
370
+
371
+ Rules for agents working on this repository: [AGENTS.md](AGENTS.md).
372
+
373
+ ## License
374
+
375
+ [MIT](LICENSE)
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The vendor of a `provider/model` id: the prefix before `/`, or the id itself when it has no `/`.
3
+ * `"deepseek/deepseek-v4-flash"` is `deepseek`, `"sonnet"` is its own vendor.
4
+ */
5
+ export function vendorFromModel(model) {
6
+ return model.split("/")[0] ?? model;
7
+ }
@@ -0,0 +1,13 @@
1
+ /** Claude Code; see docs/agents.md. */
2
+ export const claude = {
3
+ binary: "claude",
4
+ title: "Claude Code",
5
+ install: "Install Claude Code: https://docs.anthropic.com/en/docs/claude-code",
6
+ buildInvocation: (_role, model, promptFile) => ({
7
+ argv: ["claude", "-p", "--model", model, "--permission-mode", "bypassPermissions", "--no-session-persistence"],
8
+ env: {},
9
+ stdin: promptFile,
10
+ }),
11
+ vendorOf: () => "anthropic",
12
+ skillDir: () => "~/.claude/skills/gdt",
13
+ };
@@ -0,0 +1,13 @@
1
+ /** Codex CLI; see docs/agents.md. */
2
+ export const codex = {
3
+ binary: "codex",
4
+ title: "Codex",
5
+ install: "Install Codex: npm i -g @openai/codex",
6
+ buildInvocation: (_role, model, promptFile, cwd) => ({
7
+ argv: ["codex", "exec", "--model", model, "--cd", cwd, "--dangerously-bypass-approvals-and-sandbox", "--ephemeral", "-"],
8
+ env: {},
9
+ stdin: promptFile,
10
+ }),
11
+ vendorOf: () => "openai",
12
+ skillDir: () => "~/.codex/skills/gdt",
13
+ };
@@ -0,0 +1,15 @@
1
+ import { claude } from "./claude.js";
2
+ import { codex } from "./codex.js";
3
+ import { mcode } from "./mcode.js";
4
+ import { omp } from "./omp.js";
5
+ import { opencode } from "./opencode.js";
6
+ import { pi } from "./pi.js";
7
+ /** One adapter per agent; an agent with no entry has no unattended invocation (docs/agents.md). */
8
+ export const ADAPTERS = { claude, codex, opencode, mcode, pi, omp };
9
+ export function adapterFor(agent) {
10
+ return ADAPTERS[agent];
11
+ }
12
+ /** The agents gdt can actually run right now, in a stable order. */
13
+ export function supportedAgents() {
14
+ return Object.keys(ADAPTERS);
15
+ }
@@ -0,0 +1,14 @@
1
+ import { vendorFromModel } from "./adapter.js";
2
+ /** MiniMax Code; see docs/agents.md. `mcode exec --input -` reads the prompt from stdin. */
3
+ export const mcode = {
4
+ binary: "mcode",
5
+ title: "MCode",
6
+ install: "Install MiniMax Code: npm i -g @minimax-ai/code",
7
+ buildInvocation: (_role, model, promptFile, cwd) => ({
8
+ argv: ["mcode", "exec", "--model", model, "--cwd", cwd, "--permission", "full", "--input", "-"],
9
+ env: {},
10
+ stdin: promptFile,
11
+ }),
12
+ vendorOf: vendorFromModel,
13
+ skillDir: () => "~/.minimax/skills/gdt",
14
+ };
@@ -0,0 +1,14 @@
1
+ import { vendorFromModel } from "./adapter.js";
2
+ /** omp; see docs/agents.md. `--print` reads the prompt from stdin; `--auto-approve` skips approvals. */
3
+ export const omp = {
4
+ binary: "omp",
5
+ title: "omp",
6
+ install: "Install omp: https://omp.sh",
7
+ buildInvocation: (_role, model, promptFile) => ({
8
+ argv: ["omp", "--print", "--model", model, "--no-session", "--auto-approve"],
9
+ env: {},
10
+ stdin: promptFile,
11
+ }),
12
+ vendorOf: vendorFromModel,
13
+ skillDir: () => "~/.omp/agent/skills/gdt",
14
+ };
@@ -0,0 +1,15 @@
1
+ import { vendorFromModel } from "./adapter.js";
2
+ export const OPENCODE_MESSAGE = "Follow the instructions in the attached file.";
3
+ /** OpenCode; see docs/agents.md. `opencode run` documents no stdin input, so the prompt is attached as a file. */
4
+ export const opencode = {
5
+ binary: "opencode",
6
+ title: "OpenCode",
7
+ install: "Install OpenCode: https://opencode.ai",
8
+ buildInvocation: (_role, model, promptFile) => ({
9
+ argv: ["opencode", "run", "--model", model, "--auto", "--file", promptFile, OPENCODE_MESSAGE],
10
+ env: {},
11
+ stdin: null,
12
+ }),
13
+ vendorOf: vendorFromModel,
14
+ skillDir: () => "~/.config/opencode/skills/gdt",
15
+ };
@@ -0,0 +1,14 @@
1
+ import { vendorFromModel } from "./adapter.js";
2
+ /** pi; see docs/agents.md. `--print` reads the prompt from stdin and exits after one turn. */
3
+ export const pi = {
4
+ binary: "pi",
5
+ title: "pi",
6
+ install: "Install pi: npm i -g --ignore-scripts @earendil-works/pi-coding-agent",
7
+ buildInvocation: (_role, model, promptFile) => ({
8
+ argv: ["pi", "--print", "--model", model, "--no-session", "--no-approve"],
9
+ env: {},
10
+ stdin: promptFile,
11
+ }),
12
+ vendorOf: vendorFromModel,
13
+ skillDir: () => "~/.pi/agent/skills/gdt",
14
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, openSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { alive } from "../state.js";
5
+ function sleepSync(ms) {
6
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
7
+ }
8
+ function killGroup(pid, signal) {
9
+ try {
10
+ process.kill(-pid, signal);
11
+ }
12
+ catch {
13
+ try {
14
+ process.kill(pid, signal);
15
+ }
16
+ catch {
17
+ // Already gone.
18
+ }
19
+ }
20
+ }
21
+ /** Detached processes with one log file per pane under `logs`. */
22
+ export function headless(logs, cwd, env) {
23
+ return {
24
+ ensureWorkspace: () => mkdirSync(logs, { recursive: true }),
25
+ spawnPane(name, argv) {
26
+ mkdirSync(logs, { recursive: true });
27
+ const out = openSync(join(logs, `${name}.log`), "a");
28
+ const [command, ...args] = argv;
29
+ if (command === undefined)
30
+ throw new Error("spawnPane: empty argv");
31
+ const child = spawn(command, args, { cwd, env, detached: true, stdio: ["ignore", out, out] });
32
+ child.unref();
33
+ if (child.pid === undefined)
34
+ throw new Error(`could not start ${name}`);
35
+ return child.pid;
36
+ },
37
+ setTitle: () => {
38
+ // Headless panes have no title.
39
+ },
40
+ reportState: () => {
41
+ // AC-7: headless mode reports no agent state.
42
+ },
43
+ alive: (pid) => alive(pid),
44
+ attach: () => null,
45
+ close(pid) {
46
+ killGroup(pid, "SIGTERM");
47
+ for (let waited = 0; waited < 5000 && alive(pid); waited += 50)
48
+ sleepSync(50);
49
+ if (alive(pid))
50
+ killGroup(pid, "SIGKILL");
51
+ for (let waited = 0; waited < 2000 && alive(pid); waited += 50)
52
+ sleepSync(50);
53
+ },
54
+ };
55
+ }