@afokapu/atdd-bun 0.10.3 → 0.10.5
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 +7 -0
- package/conventions/delivery/delivery.board.convention.yaml +10 -11
- package/conventions/delivery/delivery.operating-model.convention.yaml +2 -2
- package/conventions/delivery/delivery.review.convention.yaml +1 -1
- package/conventions/delivery/delivery.reviewer-independent.convention.yaml +1 -1
- package/conventions/delivery/delivery.stages-complete.convention.yaml +1 -1
- package/integrity.json +10 -10
- package/package.json +1 -1
- package/src/board.ts +16 -13
- package/src/cli.ts +1 -1
- package/src/delivery.ts +10 -8
package/README.md
CHANGED
|
@@ -164,6 +164,13 @@ From 0.10.3 each ready record is judged by the delivery policy in `atdd-bun.yaml
|
|
|
164
164
|
a later policy change (dropping or reordering a model, removing a review) never makes a merged record
|
|
165
165
|
fail. Records in progress are judged by the current policy.
|
|
166
166
|
|
|
167
|
+
From 0.10.4 a stage with nothing written (a plan-only or no-op tranche) records no work; a review
|
|
168
|
+
whose range recorded none answers for the latest recorded work before it.
|
|
169
|
+
|
|
170
|
+
From 0.10.5 only coordinators and drivers use the board: a writer or reviewer takes its task from its
|
|
171
|
+
launch prompt and returns its answer as output, which the driver posts. `atdd-bun chat wait` takes several
|
|
172
|
+
topics and `--skip heartbeat`; reviewers answer in JSON.
|
|
173
|
+
|
|
167
174
|
Every key is optional; these are the defaults:
|
|
168
175
|
|
|
169
176
|
```yaml
|
|
@@ -4,11 +4,11 @@ kind: policy
|
|
|
4
4
|
status: active
|
|
5
5
|
name: Agents talk through a local board, one topic per conversation
|
|
6
6
|
statement: >-
|
|
7
|
-
Where atdd-bun.yaml sets delivery.board, the coordinator
|
|
7
|
+
Where atdd-bun.yaml sets delivery.board, the coordinator and drivers talk through a local message board with atdd-bun chat, never by typing into another agent's pane: one topic for the program, one per tranche, one per review conversation, each agent launched with its identity and the only topics it may use, every message naming its sender and recipients. The board makes the work visible; the evidence record stays the only thing the delivery rules judge.
|
|
8
8
|
terms:
|
|
9
9
|
- term_id: topics
|
|
10
10
|
text: >-
|
|
11
|
-
Three levels, each its own topic, named by atdd-bun chat topic so every agent spells them alike. atdd-<program>: coordinator, drivers and humans; status only (started, blocked, ready, merged, a decision a human must take). atdd-<program>-<tranche>: the tranche's driver
|
|
11
|
+
Three levels, each its own topic, named by atdd-bun chat topic so every agent spells them alike. atdd-<program>: coordinator, drivers and humans; status only (started, blocked, ready, merged, a decision a human must take). atdd-<program>-<tranche>: the tranche's driver; the tasks it gives its writers and their results. atdd-<program>-<tranche>-<stage>-<round>: the driver; one review's request and verdict. A rebuttal opens the next round's topic.
|
|
12
12
|
values:
|
|
13
13
|
program: atdd-bun chat topic <program>
|
|
14
14
|
tranche: atdd-bun chat topic <program> <tranche>
|
|
@@ -18,21 +18,20 @@ terms:
|
|
|
18
18
|
<role>@<tranche> for the whole tranche (driver@auth, writer-glm@auth, reviewer-codex@auth; a later round adds it: reviewer-codex-2@auth); coordinator for the coordinator.
|
|
19
19
|
- term_id: launch
|
|
20
20
|
text: >-
|
|
21
|
-
Whoever starts
|
|
21
|
+
Whoever starts a coordinator or driver gives it, in its environment, ATDD_AGENT (its identity) and ATDD_TOPICS (the topics it may use, comma-separated), and ATDD_BOARD_URL when the board is not the configured one. The coordinator gives a driver its tranche topic and the program topic; the driver adds each review topic as it opens it. atdd-bun chat refuses any other topic, to read or to write. Writers and reviewers get no board access: each is a headless run whose task is its launch prompt and whose answer is its output, which the driver posts (a verdict as "verdict of <reviewer>"), so their tools stay minimal and no sandbox has to reach the board.
|
|
22
22
|
values:
|
|
23
|
+
coordinator: ATDD_AGENT=coordinator@<program> ATDD_TOPICS=atdd-<program>
|
|
23
24
|
driver: ATDD_AGENT=driver@<tranche> ATDD_TOPICS=atdd-<program>,atdd-<program>-<tranche>
|
|
24
|
-
writer: ATDD_AGENT=writer-<model>@<tranche> ATDD_TOPICS=atdd-<program>-<tranche>
|
|
25
|
-
reviewer: ATDD_AGENT=reviewer-<model>@<tranche> ATDD_TOPICS=atdd-<program>-<tranche>-<stage>-<round>
|
|
26
25
|
- term_id: message
|
|
27
26
|
text: >-
|
|
28
|
-
atdd-bun chat post writes the header: from, to, participants and conversation always; kind (task, result, review-request, verdict, rebuttal, status, blocked), 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.
|
|
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.
|
|
29
28
|
- term_id: commands
|
|
30
29
|
text: >-
|
|
31
|
-
Post with the body on standard input; read what is on a topic; wait for the next message addressed to you, bounded by --timeout so a tool call ends (exit 2: nothing yet, wait again with --since the last id); and, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
|
|
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 again with --since the last id); and, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
|
|
32
31
|
values:
|
|
33
32
|
post: atdd-bun chat post <topic> --to <agent> --kind <kind> [--reply-to <id>] < message.md
|
|
34
33
|
read: atdd-bun chat read <topic> [--mine]
|
|
35
|
-
wait: atdd-bun chat wait <topic> [--since <id>] --timeout 540
|
|
34
|
+
wait: atdd-bun chat wait <topic>[,<topic>...] [--since <id>] [--skip heartbeat] --timeout 540
|
|
36
35
|
board: atdd-bun chat
|
|
37
36
|
- term_id: server
|
|
38
37
|
text: >-
|
|
@@ -43,11 +42,11 @@ content:
|
|
|
43
42
|
summary: >-
|
|
44
43
|
Typing into an agent's pane loses messages to startup screens, busy prompts and cut pastes. A board with one topic per conversation delivers every message, keeps the history, and lets a human follow any thread.
|
|
45
44
|
normative_text: |
|
|
46
|
-
A reviewer never reads the tranche's conversation: its brief is the review request
|
|
45
|
+
A reviewer never reads the tranche's conversation: its brief is the review request in its launch prompt, and its verdict is its output. The driver records that verdict in evidence.yaml and posts it on the review topic; the board is never evidence.
|
|
47
46
|
Agents use atdd-bun chat only. It refuses an address that is not local and a topic the agent was not given. Never call ntfy directly: in the ntfy client -u means credentials, and a topic without a server goes to the public ntfy.sh.
|
|
48
|
-
A running session that must be woken is reached through its own channel (codex queue --thread <id>; --resume <session> for Claude and Pi), never by typing into its pane;
|
|
47
|
+
A running session that must be woken is reached through its own channel (codex queue --thread <id>; --resume <session> for Claude and Pi), never by typing into its pane; a driver's launch prompt tells it to wait on its topics between steps.
|
|
49
48
|
fix_hint: |
|
|
50
|
-
Derive the topic with atdd-bun chat topic, launch
|
|
49
|
+
Derive the topic with atdd-bun chat topic, launch coordinators and drivers with ATDD_AGENT and ATDD_TOPICS, and post with --to. To follow the work, run atdd-bun chat in a terminal pane. ntfy cannot list its topics, so the first message on a topic also posts the topic's name to the directory topic atdd-topics, which the view reads.
|
|
51
50
|
exceptions:
|
|
52
51
|
- >-
|
|
53
52
|
The board is opt-in: without a delivery.board block there is none, and atdd-bun chat refuses to post or read. With the block and no url it is at http://127.0.0.1:2586; ATDD_BOARD_URL moves it on one machine but never switches it on. Switching it on or off changes how agents talk, not what a record must prove, so it is not a loosening.
|
|
@@ -14,7 +14,7 @@ terms:
|
|
|
14
14
|
Owns one tranche end to end. For each stage of delivery.stages, in order, it starts the stage's writer (the first model in its writer list, or the next after a recorded fallback) in the tranche worktree, commits red on its own before green starts, passes the stage's gate, and appends a work entry; for each stage with a reviewer it starts a review. It never reviews.
|
|
15
15
|
- term_id: review_run
|
|
16
16
|
text: >-
|
|
17
|
-
Every review is a fresh process in its own detached worktree at the exact SHA (git worktree add --detach),
|
|
17
|
+
Every review is a fresh process in its own detached worktree at the exact SHA (git worktree add --detach), launched with a prompt that gives it the delivery.review convention, the stage, the base SHA, the SHA and the RED commit. Afterwards git status --porcelain must be empty and HEAD still the SHA, or the review is a REVIEWER_FAILURE and does not count. The raw output is kept in the tranche folder and named in the review's report.
|
|
18
18
|
- term_id: fallback
|
|
19
19
|
text: >-
|
|
20
20
|
After fallback.after_failures failures within fallback.within_minutes (outage, rate limit, no auditable report, timeout), the driver uses the next model in the list and records the fallback with its role, kind, failures and window. A request-changes verdict is never a failure. With the list exhausted, when_exhausted block emits BLOCKED provider-unavailable; wait keeps retrying the last model.
|
|
@@ -74,7 +74,7 @@ content:
|
|
|
74
74
|
summary: >-
|
|
75
75
|
The policy says who may write and review each stage; this convention says how the coordinator and drivers carry it out. The record they leave is what the delivery rules judge.
|
|
76
76
|
normative_text: |
|
|
77
|
-
With delivery.board set,
|
|
77
|
+
With delivery.board set, the coordinator and drivers talk on the board (delivery.board), and each driver posts its workers' tasks, results, review requests and verdicts there; workers never use the board. Without it, a task is the prompt an agent is launched with and its result is the run's output, which the driver keeps. Agents run in panes of the multiplexer named in delivery.multiplexer (default herdr) only for humans to watch; learn its commands from its own help before the first dispatch. Never deliver work to an agent by typing into its pane.
|
|
78
78
|
Before a hosted model receives private repository content, confirm the user or organization authorized it.
|
|
79
79
|
Never edit the delivery skill, these conventions, or loosen the delivery policy to get a tranche through. If the policy must change, stop and ask the human.
|
|
80
80
|
fix_hint: |
|
|
@@ -27,7 +27,7 @@ content:
|
|
|
27
27
|
normative_text: |
|
|
28
28
|
The reviewer runs in a detached worktree at the SHA and reads the change with git diff base..SHA. The gates are CI's, not the reviewer's: it does not re-run them, and judges what they cannot. It may read, search and use git show, git diff and git log. It never edits, commits or writes, including output-file options such as git diff --output=; the driver checks the worktree afterwards, and a review that wrote does not count (delivery.reviewer-independent). It judges and proposes; the author applies.
|
|
29
29
|
A finding carrying the author's rebuttal is withdrawn, left out, when the rebuttal holds, or upheld, repeated with the same id; there is no second round (delivery.findings-resolved).
|
|
30
|
-
The reviewer returns exactly one YAML
|
|
30
|
+
The reviewer returns exactly one JSON object (valid YAML, so it goes into evidence.yaml unchanged, and a # inside a string is never read as a comment), the review entry of delivery-evidence.schema.json without fallback or report, which the driver adds: stage, sha, verdict (approve only with no critical or high finding), checked, and findings, each with id, severity (critical, high, medium, low), evidence, invariant, affects and proposed_fix, a precise description or short snippet, never a rewrite.
|
|
31
31
|
fix_hint: |
|
|
32
32
|
Give the reviewer this convention, the stage, the base SHA, the SHA and the RED commit. A review that skipped part of its checklist shows it in checked; run the stage again with a fresh reviewer.
|
|
33
33
|
exceptions:
|
|
@@ -11,7 +11,7 @@ terms:
|
|
|
11
11
|
the identity of one process: a session id, a pane id with its start time, or the retained report. Two entries with the same run were produced by the same process.
|
|
12
12
|
- term_id: independence
|
|
13
13
|
text: >-
|
|
14
|
-
fresh-process: the reviewer is a new process that wrote nothing. different-model: additionally, its model wrote none of the work it reviews; the final review answers for red, green and refactor when none of them is reviewed.
|
|
14
|
+
fresh-process: the reviewer is a new process that wrote nothing. different-model: additionally, its model wrote none of the work it reviews; the final review answers for red, green and refactor when none of them is reviewed, and a review whose range recorded no work answers for the latest recorded work before it.
|
|
15
15
|
content:
|
|
16
16
|
summary: >-
|
|
17
17
|
A reviewer that edits becomes a writer, and a reviewer that already saw an earlier round is anchored to it. Independence is what makes an approval evidence.
|
|
@@ -4,7 +4,7 @@ kind: rule
|
|
|
4
4
|
status: active
|
|
5
5
|
name: A ready record has every configured stage approved
|
|
6
6
|
statement: >-
|
|
7
|
-
A record with status ready has, for every stage the policy gives a
|
|
7
|
+
A record with status ready has, for every stage the policy gives a reviewer, a last review that approves (a stage with nothing written records no work); its approved_sha is the SHA the last review of the closing stage approved; and every review names its retained raw report, which exists (REQUIRED).
|
|
8
8
|
terms:
|
|
9
9
|
- term_id: closing_stage
|
|
10
10
|
text: >-
|
package/integrity.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.5",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
6
|
-
"README.md": "
|
|
6
|
+
"README.md": "b3d92666940ed0801e52bd8c4fd0f2942b841a52f4eceb543776d867117e2f75",
|
|
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,16 +92,16 @@
|
|
|
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": "
|
|
95
|
+
"conventions/delivery/delivery.board.convention.yaml": "c61df91a5fa95ceb77be42d0571a0f904f76ed2324cd61ae17e0d87caf42cec9",
|
|
96
96
|
"conventions/delivery/delivery.config-schema.convention.yaml": "455001b7ac12824e27b1f1dd13ae8c09bfa140957e118ebd1229779ea0808d4f",
|
|
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": "
|
|
102
|
-
"conventions/delivery/delivery.review.convention.yaml": "
|
|
103
|
-
"conventions/delivery/delivery.reviewer-independent.convention.yaml": "
|
|
104
|
-
"conventions/delivery/delivery.stages-complete.convention.yaml": "
|
|
101
|
+
"conventions/delivery/delivery.operating-model.convention.yaml": "e66f75787518954cd830fff544d40ac7ddecdf8cf58aa1d73a76a30beeff3fe9",
|
|
102
|
+
"conventions/delivery/delivery.review.convention.yaml": "b033579c2d90fcb098d325dcc07c7b084e048cca43fbe8e28159ec08a412018a",
|
|
103
|
+
"conventions/delivery/delivery.reviewer-independent.convention.yaml": "8c436d66ef5b1f6d6bc5dd55a235caca2490020e88665eecadb2ca41cd4149c5",
|
|
104
|
+
"conventions/delivery/delivery.stages-complete.convention.yaml": "0e5dd8057ad51eff7906e3424a9686a81be1814447afb9a30acab43d39e49b4c",
|
|
105
105
|
"conventions/planner.docs/planner.docs.adr-registry-derived.convention.yaml": "0ea4232beba90280c664f93d097cafa44c67c2919c1468d4823fdc9e686aa5d2",
|
|
106
106
|
"conventions/planner.docs/planner.docs.area-index-required.convention.yaml": "fd931e9f225bfbb5e78436d0d8a2a89cc0f5114493579962c72dd971301becce",
|
|
107
107
|
"conventions/planner.docs/planner.docs.artifact-path-shape.convention.yaml": "a9f6178d29abe9385342a46a2c57470a3631aaf5e26f01e3660b7088b042418e",
|
|
@@ -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": "dede8d3059165a1a0fd051420d83ef91ff9a5fffa57d1ff6c44f31defa393bfa",
|
|
1196
1196
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1197
|
-
"src/cli.ts": "
|
|
1198
|
-
"src/delivery.ts": "
|
|
1197
|
+
"src/cli.ts": "e45f2666334503a91eb65b1b5ee4dd8acab6b99f62d333e2ee2b65963db6358b",
|
|
1198
|
+
"src/delivery.ts": "3fd36b6f7dbe9b5682241cff16362618ac0a85c0ac93a238141ebabdbe402e52",
|
|
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
|
@@ -103,14 +103,16 @@ export async function read(url: string, topics: string[], since = "all"): Promis
|
|
|
103
103
|
.map(m => ({ id: m.id, time: m.time, topic: m.topic, raw: m.message, ...parseEnvelope(m.message) }));
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/** Waits for the next message addressed to `agent`
|
|
107
|
-
* a message published while it connects.
|
|
108
|
-
|
|
106
|
+
/** Waits for the next message addressed to `agent` on any of `topics` after `since`, passing over the kinds in `skip`.
|
|
107
|
+
* It polls rather than streams: a live stream can drop a message published while it connects. ntfy reads `since` (a
|
|
108
|
+
* message id) as that message's time on every topic, so one cursor serves several topics. Returns null after
|
|
109
|
+
* `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> {
|
|
109
111
|
const deadline = timeoutSeconds > 0 ? Date.now() + timeoutSeconds * 1000 : Infinity;
|
|
110
112
|
for (;;) {
|
|
111
113
|
let messages: Message[] = [];
|
|
112
|
-
try { messages = await read(url,
|
|
113
|
-
for (const message of messages) { since = message.id; if (addressedTo(message, agent)) return message; }
|
|
114
|
+
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; }
|
|
114
116
|
if (Date.now() >= deadline) return null;
|
|
115
117
|
await Bun.sleep(intervalMs);
|
|
116
118
|
}
|
|
@@ -136,9 +138,16 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
136
138
|
if (command === undefined) return await runUi(url, env);
|
|
137
139
|
const topics = list(positional[0]);
|
|
138
140
|
if (!topics.length) throw new Error(`usage: atdd-bun chat ${command} <topic> …`);
|
|
141
|
+
const me = agent();
|
|
142
|
+
topics.forEach(topic => checkTopic(topic, env));
|
|
143
|
+
if (command === "wait") {
|
|
144
|
+
const message = await waitFor(url, topics, me, flag("since") || "all", Number(flag("timeout") ?? 0), list(flag("skip")));
|
|
145
|
+
if (!message) { console.error(`no message for ${me} on ${topics.join(", ")} yet; wait again with --since to continue`); return 2; }
|
|
146
|
+
console.log(format(message, topics.length > 1));
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
139
149
|
if (topics.length !== 1) throw new Error(`${command} takes one topic`);
|
|
140
|
-
const topic = topics[0]
|
|
141
|
-
checkTopic(topic, env);
|
|
150
|
+
const topic = topics[0];
|
|
142
151
|
if (command === "post") {
|
|
143
152
|
const to = list(flag("to"));
|
|
144
153
|
if (!to.length) throw new Error("--to is required: every message names its recipients");
|
|
@@ -153,12 +162,6 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
153
162
|
for (const message of await read(url, [topic], flag("since") || "all")) if (!rest.includes("--mine") || addressedTo(message, me)) console.log(format(message));
|
|
154
163
|
return 0;
|
|
155
164
|
}
|
|
156
|
-
if (command === "wait") {
|
|
157
|
-
const message = await waitFor(url, topic, me, flag("since") || "all", Number(flag("timeout") ?? 0));
|
|
158
|
-
if (!message) { console.error(`no message for ${me} on ${topic} yet; wait again with --since to continue`); return 2; }
|
|
159
|
-
console.log(format(message));
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
165
|
throw new Error(`unknown chat command ${command}; run atdd-bun chat alone for the board, or topic, post, read, wait`);
|
|
163
166
|
} catch (error) {
|
|
164
167
|
console.error(error instanceof Error ? error.message : String(error));
|
package/src/cli.ts
CHANGED
|
@@ -25,7 +25,7 @@ const usage = {
|
|
|
25
25
|
"atdd-bun chat topic <program> [<tranche> [<stage> <round>]]",
|
|
26
26
|
"atdd-bun chat post <topic> --to <agent,...> [--kind K] [--stage S] [--reply-to ID] [--repo R] [--branch B] [--worktree W] [--goal G] (body on stdin)",
|
|
27
27
|
"atdd-bun chat read <topic> [--mine] [--since ID]",
|
|
28
|
-
"atdd-bun chat wait <topic> [--since ID] [--timeout SECONDS]",
|
|
28
|
+
"atdd-bun chat wait <topic[,topic...]> [--since ID] [--skip KIND,...] [--timeout SECONDS]",
|
|
29
29
|
"atdd-bun chat (the board: every topic on the left, the conversation on the right)",
|
|
30
30
|
],
|
|
31
31
|
profiles: profileNames,
|
package/src/delivery.ts
CHANGED
|
@@ -258,12 +258,17 @@ function checkFallbacks(file: string, at: string, fallbacks: Fallback[], fallbac
|
|
|
258
258
|
return out;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
/** The stages a review of `stage` answers for: its own, and every stage after the previous reviewed one
|
|
262
|
-
* covers red, green and refactor when none of them is reviewed.
|
|
263
|
-
|
|
261
|
+
/** The stages a review of `stage` answers for: its own, and every stage after the previous reviewed one (the final review
|
|
262
|
+
* covers red, green and refactor when none of them is reviewed). A stage with nothing written records nothing; when no
|
|
263
|
+
* work is recorded in that range, the review answers for the latest recorded work before it, so a plan-only final review
|
|
264
|
+
* is still independent of the plan's writer. */
|
|
265
|
+
function coveredStages(stage: Stage, policy: DeliveryPolicy, work: Work[]): Stage[] {
|
|
264
266
|
const position = STAGES.indexOf(stage);
|
|
265
267
|
const previous = STAGES.slice(0, position).findLastIndex(name => (policy.stages[name]?.reviewer.length ?? 0) > 0);
|
|
266
|
-
|
|
268
|
+
const range = STAGES.slice(previous + 1, position + 1);
|
|
269
|
+
if (work.some(entry => range.includes(entry.stage))) return range;
|
|
270
|
+
const latest = STAGES.slice(0, previous + 1).findLast(name => work.some(entry => entry.stage === name));
|
|
271
|
+
return latest ? [latest] : range;
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
/** Reports are data, never code: a report path is exempt from drift, so it must not be able to name a source file. */
|
|
@@ -303,7 +308,7 @@ function checkEvidence(file: string, evidence: Evidence, policy: DeliveryPolicy)
|
|
|
303
308
|
out.push(...checkFallbacks(file, at, fallbacks, policy.fallback), ...checkModel(file, at, review.stage, "reviewer", review.reviewer, stage.reviewer, fallbacks, "reviewer", policy.fallback));
|
|
304
309
|
if (review.author && stage.writer.length) out.push(...checkModel(file, at, review.stage, "writer", review.author, stage.writer, fallbacks, "reviewer", policy.fallback));
|
|
305
310
|
// Independence is judged against everyone who wrote what this review answers for.
|
|
306
|
-
const covered = coveredStages(review.stage, policy), writers = [...work.filter(entry => covered.includes(entry.stage)).map(entry => entry.writer), ...(review.author ? [review.author] : [])];
|
|
311
|
+
const covered = coveredStages(review.stage, policy, work), writers = [...work.filter(entry => covered.includes(entry.stage)).map(entry => entry.writer), ...(review.author ? [review.author] : [])];
|
|
307
312
|
if (!writers.length) out.push(finding("delivery.evidence-schema", file, `${at}: names no writer of the work it reviews (${covered.join(", ")}); record each stage's work, or the review's author, so independence can be judged`));
|
|
308
313
|
if (writerRuns.has(review.reviewer.run)) out.push(finding("delivery.reviewer-independent", file, `${at}: reviewer run '${review.reviewer.run}' also wrote in this tranche; a reviewer that edits becomes a writer`));
|
|
309
314
|
if (reviewerRuns.has(review.reviewer.run)) out.push(finding("delivery.reviewer-independent", file, `${at}: reviewer run '${review.reviewer.run}' already reviewed reviews[${reviewerRuns.get(review.reviewer.run)}]; every review is a fresh process`));
|
|
@@ -339,9 +344,6 @@ function checkEvidence(file: string, evidence: Evidence, policy: DeliveryPolicy)
|
|
|
339
344
|
if (evidence.status === "ready") {
|
|
340
345
|
const configured = STAGES.filter(stage => policy.stages[stage]);
|
|
341
346
|
for (const stage of configured) {
|
|
342
|
-
// A 0.9 record names writers as the authors of the reviews that covered their stages.
|
|
343
|
-
const written = work.some(entry => entry.stage === stage) || reviews.some(review => review.author && coveredStages(review.stage, policy).includes(stage));
|
|
344
|
-
if (policy.stages[stage]!.writer.length && !written) out.push(finding("delivery.stages-complete", file, `status is ready, but ${stage} records no work; name who wrote it`));
|
|
345
347
|
if (!policy.stages[stage]!.reviewer.length) continue;
|
|
346
348
|
const last = reviews.filter(review => review.stage === stage).at(-1);
|
|
347
349
|
if (!last) out.push(finding("delivery.stages-complete", file, `status is ready, but ${stage} has no review`));
|