@afokapu/atdd-bun 0.10.5 → 0.10.6
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 +5 -5
- package/conventions/delivery/delivery.board.convention.yaml +1 -1
- package/conventions/delivery/delivery.config-schema.convention.yaml +5 -5
- package/conventions/delivery/delivery.operating-model.convention.yaml +7 -1
- package/integrity.json +7 -7
- package/package.json +1 -1
- package/src/board.ts +17 -5
- package/src/delivery.ts +5 -5
package/README.md
CHANGED
|
@@ -181,11 +181,11 @@ delivery:
|
|
|
181
181
|
# board: { url: http://127.0.0.1:2586 } # opt-in: agents talk through a local board; absent, there is none
|
|
182
182
|
independence: different-model # a reviewer's model wrote none of the work it reviews; or fresh-process
|
|
183
183
|
stages: # models in preference order: the first, then recorded fallbacks
|
|
184
|
-
plan: { writer: [codex, claude-opus], reviewer: [glm, claude-opus, codex] }
|
|
185
|
-
red: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
186
|
-
green: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
187
|
-
refactor: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
188
|
-
final: { reviewer: [codex, glm, claude-opus] }
|
|
184
|
+
plan: { writer: [codex, claude-opus, kimi], reviewer: [glm, claude-opus, codex, kimi] }
|
|
185
|
+
red: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
186
|
+
green: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
187
|
+
refactor: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
188
|
+
final: { reviewer: [codex, glm, claude-opus, kimi] }
|
|
189
189
|
fallback: { after_failures: 3, within_minutes: 10, when_exhausted: block } # or wait
|
|
190
190
|
commands: {} # per model: { author: "...", review: "..." } overriding delivery.operating-model's defaults
|
|
191
191
|
```
|
|
@@ -27,7 +27,7 @@ terms:
|
|
|
27
27
|
atdd-bun chat post writes the header: from, to, participants and conversation always; kind (task, result, review-request, verdict, rebuttal, status, blocked, heartbeat), stage, reply-to, repo, branch, worktree, goal, base and sha when they apply. The body carries the whole instruction or answer, however long: the thread is the history. A heartbeat carries nothing else; anything that needs an answer goes as blocked, never inside a heartbeat.
|
|
28
28
|
- term_id: commands
|
|
29
29
|
text: >-
|
|
30
|
-
Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet
|
|
30
|
+
Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet). wait remembers where each identity stopped on each set of topics (~/.atdd-board/cursors), so re-arming a watcher is always the same command; --since still overrides it; and, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
|
|
31
31
|
values:
|
|
32
32
|
post: atdd-bun chat post <topic> --to <agent> --kind <kind> [--reply-to <id>] < message.md
|
|
33
33
|
read: atdd-bun chat read <topic> [--mine]
|
|
@@ -21,11 +21,11 @@ content:
|
|
|
21
21
|
root: docs/delivery/tranches
|
|
22
22
|
independence: different-model
|
|
23
23
|
stages:
|
|
24
|
-
plan: { writer: [codex, claude-opus], reviewer: [glm, claude-opus, codex] }
|
|
25
|
-
red: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
26
|
-
green: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
27
|
-
refactor: { writer: [glm, claude-sonnet, claude-opus, codex] }
|
|
28
|
-
final: { reviewer: [codex, glm, claude-opus] }
|
|
24
|
+
plan: { writer: [codex, claude-opus, kimi], reviewer: [glm, claude-opus, codex, kimi] }
|
|
25
|
+
red: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
26
|
+
green: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
27
|
+
refactor: { writer: [glm, claude-sonnet, deepseek-flash, claude-opus, codex, kimi] }
|
|
28
|
+
final: { reviewer: [codex, glm, claude-opus, kimi] }
|
|
29
29
|
|
|
30
30
|
independence defaults to different-model. A stage may name a writer, a reviewer or both. The 0.9
|
|
31
31
|
names plan_review, test_review, code_review and final_review are still read, as plan, red, refactor
|
|
@@ -29,7 +29,7 @@ terms:
|
|
|
29
29
|
One line each, for the coordinator to wait on: PROGRAM_EVENT <tranche> <STAGE <stage> <sha> | WORKER_START <role> <model> <sha> | WORKER_END <role> <model> <verdict> | FALLBACK <role> <from>→<to> <reason> | PR_OPENED <url> | MERGED <sha> | BLOCKED <reason> | HEARTBEAT>.
|
|
30
30
|
- term_id: commands
|
|
31
31
|
text: >-
|
|
32
|
-
The default headless command per model, overridden per model under delivery.commands.<model>.author (the writer's command) and .review. {prompt} and {worktree} are substituted. Claude runs take project settings only, so a user's own allowances cannot widen a reviewer. glm
|
|
32
|
+
The default headless command per model, overridden per model under delivery.commands.<model>.author (the writer's command) and .review. {prompt} and {worktree} are substituted. Claude runs take project settings only, so a user's own allowances cannot widen a reviewer. glm, kimi (Moonshot kimi-k3, a replacement for codex and claude-opus) and deepseek-flash (a smaller coding model beside claude-sonnet) run through Pi, which has no permission system; kimi and deepseek-flash take their keys from MOONSHOT_API_KEY and DEEPSEEK_API_KEY, or point delivery.commands at a wrapper that provides them. Through Pi, as a reviewer it gets the read tool alone (the driver puts the diff in its prompt and records its output), and as a writer it runs only inside a container or sandbox limited to the worktree.
|
|
33
33
|
values:
|
|
34
34
|
codex:
|
|
35
35
|
author: codex exec --cd {worktree} --sandbox workspace-write "{prompt}"
|
|
@@ -43,6 +43,12 @@ terms:
|
|
|
43
43
|
glm:
|
|
44
44
|
author: cd {worktree} && pi -p --no-session --no-extensions --no-skills --no-context-files --tools read,bash,edit,write "{prompt}"
|
|
45
45
|
review: cd {worktree} && pi -p --no-session --no-extensions --no-skills --no-context-files --tools read "{prompt}"
|
|
46
|
+
kimi:
|
|
47
|
+
author: cd {worktree} && pi --provider moonshotai --model kimi-k3 -p --no-session --no-extensions --no-skills --no-context-files --tools read,bash,edit,write "{prompt}"
|
|
48
|
+
review: cd {worktree} && pi --provider moonshotai --model kimi-k3 -p --no-session --no-extensions --no-skills --no-context-files --tools read "{prompt}"
|
|
49
|
+
deepseek-flash:
|
|
50
|
+
author: cd {worktree} && pi --provider deepseek --model deepseek-flash -p --no-session --no-extensions --no-skills --no-context-files --tools read,bash,edit,write "{prompt}"
|
|
51
|
+
review: cd {worktree} && pi --provider deepseek --model deepseek-flash -p --no-session --no-extensions --no-skills --no-context-files --tools read "{prompt}"
|
|
46
52
|
- term_id: example_record
|
|
47
53
|
text: >-
|
|
48
54
|
A tranche in progress: the plan written by codex and approved by glm; red, green and refactor written by glm; the final review requesting changes. Only the finding's outcome is ever added to an earlier entry.
|
package/integrity.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.6",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
6
|
-
"README.md": "
|
|
6
|
+
"README.md": "b70a7002a8f887ed0108fc22b3bce15af438df1f094e83e8abc334547cd9e95d",
|
|
7
7
|
"bunfig.toml": "b9fc65eca9014c5179380259d70e76385a6a80788fa9a2df5fb3eaa5554fd2fe",
|
|
8
8
|
"conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml": "81c5c773d5ee15e8d99a2c237aa9846b110b2533df45ebf407d0e3ec6ed3dd03",
|
|
9
9
|
"conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml": "e96d7c1455d0c221072d82e7f2b55da1c4f9ceb17718eb6ed96affdeef37675b",
|
|
@@ -92,13 +92,13 @@
|
|
|
92
92
|
"conventions/coder.htmx/coder.htmx.verb-endpoint-same-origin.convention.yaml": "2128d26153e2d1f19c65a1e1665d4f82c78395daedb336d6ef1256e82b74aedb",
|
|
93
93
|
"conventions/coder.htmx/coder.htmx.verb-mutation-signals-progress.convention.yaml": "ae85c5ea01a09a3c41b939988088a8e5da91848d2710a1f0a1e0c86308638ac9",
|
|
94
94
|
"conventions/delivery/delivery.approved-sha-resolves.convention.yaml": "12eadbc75b081e9469b9a0a7dbf3fdd30c8b34f66e647efa8bf2528119e61547",
|
|
95
|
-
"conventions/delivery/delivery.board.convention.yaml": "
|
|
96
|
-
"conventions/delivery/delivery.config-schema.convention.yaml": "
|
|
95
|
+
"conventions/delivery/delivery.board.convention.yaml": "51ff4c16dddf20504f34accc5ddcd150108fab70e4e6f5bdb36cec71d0c905a8",
|
|
96
|
+
"conventions/delivery/delivery.config-schema.convention.yaml": "4c7b449074a8bd9f89b70c702ab3e0df229105ddb4458772f894f0bf37813e9f",
|
|
97
97
|
"conventions/delivery/delivery.evidence-schema.convention.yaml": "9c6a22495dfb3e2fcbca700392866c3b5660084b1f5594230eb320b499d5e5cb",
|
|
98
98
|
"conventions/delivery/delivery.findings-resolved.convention.yaml": "f6574e93a9a2d4bdd6d6f5645720b1a647fc0048d95f7675fe783d2ddb62c05b",
|
|
99
99
|
"conventions/delivery/delivery.merge-gate.convention.yaml": "48745ec23625b68be9b8fe2c6dcdcba88b2b38924f6593abf6987391ff0ee243",
|
|
100
100
|
"conventions/delivery/delivery.model-allowed.convention.yaml": "1071a727f61bacd21d70921885826974234ef0a58c0a814454b9375a0432b0fe",
|
|
101
|
-
"conventions/delivery/delivery.operating-model.convention.yaml": "
|
|
101
|
+
"conventions/delivery/delivery.operating-model.convention.yaml": "7982157d957938ade878a114d3f5c2bdaa8f02e52ac21614634d9af4be9692f1",
|
|
102
102
|
"conventions/delivery/delivery.review.convention.yaml": "b033579c2d90fcb098d325dcc07c7b084e048cca43fbe8e28159ec08a412018a",
|
|
103
103
|
"conventions/delivery/delivery.reviewer-independent.convention.yaml": "8c436d66ef5b1f6d6bc5dd55a235caca2490020e88665eecadb2ca41cd4149c5",
|
|
104
104
|
"conventions/delivery/delivery.stages-complete.convention.yaml": "0e5dd8057ad51eff7906e3424a9686a81be1814447afb9a30acab43d39e49b4c",
|
|
@@ -1192,10 +1192,10 @@
|
|
|
1192
1192
|
"relationships.yaml": "97da2db334e3c8cd4f634ee8d12fe426aabc0f5c37508d02450ce8beca100f04",
|
|
1193
1193
|
"src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
|
|
1194
1194
|
"src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
|
|
1195
|
-
"src/board.ts": "
|
|
1195
|
+
"src/board.ts": "115c6cff12fb2b20e6471013161aef020a65331ecb97fa323e9b08f55053149f",
|
|
1196
1196
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1197
1197
|
"src/cli.ts": "e45f2666334503a91eb65b1b5ee4dd8acab6b99f62d333e2ee2b65963db6358b",
|
|
1198
|
-
"src/delivery.ts": "
|
|
1198
|
+
"src/delivery.ts": "9a4500ae949faeef7bcfda114b7b09d4ac2b2cbb1218e68016829e0b6892847b",
|
|
1199
1199
|
"src/docs-capability.ts": "115cf27049a5cf19c133bac5ec237072be6f969a59a2ef6767e054108a21dc5c",
|
|
1200
1200
|
"src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
|
|
1201
1201
|
"src/hooks.ts": "9779eb48f9d0cb6234c33f6616f98dde6583d2ee03ac3c19f3ace2d156af26d0",
|
package/package.json
CHANGED
package/src/board.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { runUi } from "./board-ui";
|
|
@@ -107,17 +108,26 @@ export async function read(url: string, topics: string[], since = "all"): Promis
|
|
|
107
108
|
* It polls rather than streams: a live stream can drop a message published while it connects. ntfy reads `since` (a
|
|
108
109
|
* message id) as that message's time on every topic, so one cursor serves several topics. Returns null after
|
|
109
110
|
* `timeoutSeconds`, so an agent's tool call stays bounded. */
|
|
110
|
-
export async function waitFor(url: string, topics: string[], agent: string, since = "all", timeoutSeconds = 0, skip: string[] = [], intervalMs = 500): Promise<Message | null> {
|
|
111
|
+
export async function waitFor(url: string, topics: string[], agent: string, since = "all", timeoutSeconds = 0, skip: string[] = [], intervalMs = 500, seen: (id: string) => void = () => {}): Promise<Message | null> {
|
|
111
112
|
const deadline = timeoutSeconds > 0 ? Date.now() + timeoutSeconds * 1000 : Infinity;
|
|
112
113
|
for (;;) {
|
|
113
114
|
let messages: Message[] = [];
|
|
114
115
|
try { messages = await read(url, topics, since); } catch { /* the board is briefly unavailable: keep waiting */ }
|
|
115
|
-
for (const message of messages) { since = message.id; if (addressedTo(message, agent) && !skip.includes(message.header.kind ?? "")) return message; }
|
|
116
|
+
for (const message of messages) { since = message.id; seen(since); if (addressedTo(message, agent) && !skip.includes(message.header.kind ?? "")) return message; }
|
|
116
117
|
if (Date.now() >= deadline) return null;
|
|
117
118
|
await Bun.sleep(intervalMs);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/** Where `wait` keeps its place, per identity and topic set, so restarting a watcher is always the same command.
|
|
123
|
+
* Best effort: where the file cannot be read or written, wait behaves as if it had no saved place. */
|
|
124
|
+
function cursorFile(agent: string, topics: string[], env: Record<string, string | undefined>): string {
|
|
125
|
+
const key = `${agent}__${[...topics].sort().join("+")}`.replace(/[^A-Za-z0-9@+_.-]/g, "_");
|
|
126
|
+
return join(env.ATDD_BOARD_STATE || join(homedir(), ".atdd-board", "cursors"), key);
|
|
127
|
+
}
|
|
128
|
+
const loadCursor = (file: string) => { try { return readFileSync(file, "utf8").trim() || undefined; } catch { return undefined; } };
|
|
129
|
+
const saveCursor = (file: string, id: string) => { try { mkdirSync(join(file, ".."), { recursive: true }); writeFileSync(file, `${id}\n`); } catch { /* best effort */ } };
|
|
130
|
+
|
|
121
131
|
export function format(message: Message, withTopic = false): string {
|
|
122
132
|
const time = new Date(message.time * 1000).toTimeString().slice(0, 8), h = message.header;
|
|
123
133
|
const route = `${h.from ?? "?"} -> ${[h.to ?? []].flat().join(", ") || "?"}`, tags = [h.kind, h.stage].filter(Boolean).join(" · ");
|
|
@@ -141,8 +151,10 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
141
151
|
const me = agent();
|
|
142
152
|
topics.forEach(topic => checkTopic(topic, env));
|
|
143
153
|
if (command === "wait") {
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
// Without --since, continue from where this identity last stopped on these topics; either way, remember the place.
|
|
155
|
+
const file = cursorFile(me, topics, env);
|
|
156
|
+
const message = await waitFor(url, topics, me, flag("since") || loadCursor(file) || "all", Number(flag("timeout") ?? 0), list(flag("skip")), 500, id => saveCursor(file, id));
|
|
157
|
+
if (!message) { console.error(`no message for ${me} on ${topics.join(", ")} yet; run the same wait again to continue`); return 2; }
|
|
146
158
|
console.log(format(message, topics.length > 1));
|
|
147
159
|
return 0;
|
|
148
160
|
}
|
package/src/delivery.ts
CHANGED
|
@@ -41,11 +41,11 @@ export type DeliveryPolicy = {
|
|
|
41
41
|
/** The default operating model: two reviews, the plan and the whole change; the stages between them are written and
|
|
42
42
|
* held by their deterministic gates. Lists are preference orders; a later model is used only as a recorded fallback. */
|
|
43
43
|
const DEFAULT_STAGES: Partial<Record<Stage, { writer?: string[]; reviewer?: string[] }>> = {
|
|
44
|
-
plan: { writer: ["codex", "claude-opus"], reviewer: ["glm", "claude-opus", "codex"] },
|
|
45
|
-
red: { writer: ["glm", "claude-sonnet", "claude-opus", "codex"] },
|
|
46
|
-
green: { writer: ["glm", "claude-sonnet", "claude-opus", "codex"] },
|
|
47
|
-
refactor: { writer: ["glm", "claude-sonnet", "claude-opus", "codex"] },
|
|
48
|
-
final: { reviewer: ["codex", "glm", "claude-opus"] },
|
|
44
|
+
plan: { writer: ["codex", "claude-opus", "kimi"], reviewer: ["glm", "claude-opus", "codex", "kimi"] },
|
|
45
|
+
red: { writer: ["glm", "claude-sonnet", "deepseek-flash", "claude-opus", "codex", "kimi"] },
|
|
46
|
+
green: { writer: ["glm", "claude-sonnet", "deepseek-flash", "claude-opus", "codex", "kimi"] },
|
|
47
|
+
refactor: { writer: ["glm", "claude-sonnet", "deepseek-flash", "claude-opus", "codex", "kimi"] },
|
|
48
|
+
final: { reviewer: ["codex", "glm", "claude-opus", "kimi"] },
|
|
49
49
|
};
|
|
50
50
|
/** The stage a record or config name refers to: a current name, or a legacy review stage's. */
|
|
51
51
|
export const stageOf = (name: string): Stage | undefined => (STAGES as readonly string[]).includes(name) ? name as Stage : LEGACY_STAGES[name];
|