@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.
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/agents/adapter.js +7 -0
- package/dist/agents/claude.js +13 -0
- package/dist/agents/codex.js +13 -0
- package/dist/agents/index.js +15 -0
- package/dist/agents/mcode.js +14 -0
- package/dist/agents/omp.js +14 -0
- package/dist/agents/opencode.js +15 -0
- package/dist/agents/pi.js +14 -0
- package/dist/backends/backend.js +1 -0
- package/dist/backends/headless.js +55 -0
- package/dist/backends/herdr.js +286 -0
- package/dist/backends/index.js +20 -0
- package/dist/cli.js +466 -0
- package/dist/config.js +210 -0
- package/dist/contract.js +133 -0
- package/dist/decision.js +175 -0
- package/dist/doctor.js +231 -0
- package/dist/finding.js +3 -0
- package/dist/git.js +25 -0
- package/dist/github.js +95 -0
- package/dist/locale.js +44 -0
- package/dist/notify.js +29 -0
- package/dist/prompts.js +80 -0
- package/dist/protocol.js +114 -0
- package/dist/state.js +113 -0
- package/dist/steering.js +165 -0
- package/dist/supervisor.js +369 -0
- package/dist/worker.js +232 -0
- package/dist/workflow.js +333 -0
- package/locales/en.toml +28 -0
- package/locales/nl.toml +28 -0
- package/package.json +61 -0
- package/roles/developer.md +66 -0
- package/roles/issue-writer.md +40 -0
- package/roles/reviewer.md +65 -0
- package/roles/tester.md +58 -0
- package/schemas/gdt-answer.v1.schema.json +32 -0
- package/schemas/gdt-directive.v1.schema.json +36 -0
- package/schemas/gdt-handoff.v1.schema.json +117 -0
- package/schemas/gdt-question.v1.schema.json +82 -0
- package/schemas/gdt-review.v1.schema.json +131 -0
- package/schemas/gdt-round.v1.schema.json +28 -0
- package/schemas/gdt-test.v1.schema.json +131 -0
- 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
|
+
}
|