agent-coord-mcp 0.26.18 → 0.26.19
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/dist/build.js +113 -0
- package/dist/build.js.map +1 -0
- package/dist/capabilities.js +158 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/prefix.js +64 -0
- package/dist/prefix.js.map +1 -0
- package/dist/roles.js +132 -0
- package/dist/roles.js.map +1 -0
- package/dist/server-identity.js +82 -0
- package/dist/server-identity.js.map +1 -0
- package/dist/server.js +647 -0
- package/dist/server.js.map +1 -0
- package/dist/store.js +553 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/admin.js +323 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/attention.js +73 -0
- package/dist/tools/attention.js.map +1 -0
- package/dist/tools/away.js +285 -0
- package/dist/tools/away.js.map +1 -0
- package/dist/tools/board-ref.js +208 -0
- package/dist/tools/board-ref.js.map +1 -0
- package/dist/tools/event-kinds.js +39 -0
- package/dist/tools/event-kinds.js.map +1 -0
- package/dist/tools/events.js +234 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/logwatch.js +85 -0
- package/dist/tools/logwatch.js.map +1 -0
- package/dist/tools/messaging.js +706 -0
- package/dist/tools/messaging.js.map +1 -0
- package/dist/tools/record-events.js +380 -0
- package/dist/tools/record-events.js.map +1 -0
- package/dist/tools/records.js +1027 -0
- package/dist/tools/records.js.map +1 -0
- package/dist/tools/registry.js +500 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/render.js +2 -0
- package/dist/tools/render.js.map +1 -0
- package/dist/tools/rooms.js +210 -0
- package/dist/tools/rooms.js.map +1 -0
- package/dist/tools/rotate.js +143 -0
- package/dist/tools/rotate.js.map +1 -0
- package/dist/tools/scopes.js +126 -0
- package/dist/tools/scopes.js.map +1 -0
- package/dist/tools/shared.js +104 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/stall.js +414 -0
- package/dist/tools/stall.js.map +1 -0
- package/dist/tools/transport.js +1878 -0
- package/dist/tools/transport.js.map +1 -0
- package/dist/tools/work.js +373 -0
- package/dist/tools/work.js.map +1 -0
- package/dist/tools/worktrees.js +581 -0
- package/dist/tools/worktrees.js.map +1 -0
- package/dist/typed-records.js +174 -0
- package/dist/typed-records.js.map +1 -0
- package/dist/work.js +2 -0
- package/dist/work.js.map +1 -0
- package/hooks/peek-coord.mjs +0 -0
- package/hooks/tmux-pusher.mjs +0 -0
- package/package.json +11 -14
- package/scripts/coord-attention-clock.mjs +0 -0
- package/scripts/coord-node.sh +0 -0
- package/scripts/coord-stall-clock.mjs +0 -0
- package/scripts/coord-token.mjs +0 -0
- package/scripts/probe-tmux-liveness.sh +0 -0
- package/scripts/spawn-agent.sh +0 -0
- package/scripts/stop-agent.sh +0 -0
- package/scripts/typed-record-stats.mjs +0 -0
- package/src/server.ts +9 -1
- package/src/tools/worktrees.ts +234 -32
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `coord_away` — DAVID IS AWAY AND THE AIDE LEADS.
|
|
3
|
+
*
|
|
4
|
+
* REDEFINED (David, 2026-08-30). The verb used to mean "the coordinator stepped
|
|
5
|
+
* out and a duty officer keeps its seat warm". That meaning is RETIRED, not
|
|
6
|
+
* deprecated: same skill label, one meaning. A verb with two meanings is read
|
|
7
|
+
* as whichever one the reader already had in mind, and the two differ on who
|
|
8
|
+
* holds authority — the most expensive thing to be ambiguous about.
|
|
9
|
+
*
|
|
10
|
+
* WHAT IT NOW MEANS: David is away, and the aide LEADS in his absence. That is
|
|
11
|
+
* a grant of DECISION authority, not of execution authority, and the boundary
|
|
12
|
+
* is the feature:
|
|
13
|
+
*
|
|
14
|
+
* DECIDES planning · priority · queue curation · roadmap · canon · releases
|
|
15
|
+
* under standing publish authorisation.
|
|
16
|
+
* NEVER merges, gates, or takes a code lane. Unchanged by the grant — a
|
|
17
|
+
* lead is not a second worker, and the aide's nevers are what keep
|
|
18
|
+
* the gate non-author.
|
|
19
|
+
* PARKS licence · funding · public-vs-private · credentials · anything
|
|
20
|
+
* altering scope or authority · destructive machine actions.
|
|
21
|
+
*
|
|
22
|
+
* WHY PARK RATHER THAN REFUSE-AND-FORGET: a parked decision is one David
|
|
23
|
+
* returns to. The whole point of leading in an absence is that the absence ends,
|
|
24
|
+
* and the fleet should hand back a DIFF rather than a transcript — which is why
|
|
25
|
+
* `decisionLog` is required to turn this on rather than being a nice-to-have.
|
|
26
|
+
*
|
|
27
|
+
* THE PRECONDITION IS MEASURED COVERAGE, NOT A RUN. Going away is a promise
|
|
28
|
+
* that the fleet is observed while nobody is watching, and the fleet was
|
|
29
|
+
* measured making that promise with a stall clock that had been stopped for
|
|
30
|
+
* eight hours — and then, once started, reporting `checked 3 · measurable 0`.
|
|
31
|
+
* "The clock ran" and "the fleet is observed" are different facts. So the
|
|
32
|
+
* precondition asks what the clock could actually SEE, and going away blind is
|
|
33
|
+
* a deliberate, recorded act rather than a silent default.
|
|
34
|
+
*/
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
import { ROOT } from "../store.js";
|
|
39
|
+
import { stallCheckTool } from "./stall.js";
|
|
40
|
+
const awayFile = () => path.join(ROOT, "coord-away.json");
|
|
41
|
+
/**
|
|
42
|
+
* WHAT THE LEAD MAY NOT DO, enforced rather than trusted to a card line.
|
|
43
|
+
*
|
|
44
|
+
* ALLOWLIST FOR THE OLD MEANING, DENYLIST FOR THIS ONE — and the asymmetry is
|
|
45
|
+
* deliberate rather than a relaxation. A duty officer's grant was NARROW ("keep
|
|
46
|
+
* claimed work moving"), so absence had to mean refusal or every unlisted tool
|
|
47
|
+
* was a silent grant. A lead's grant is WIDE by design: it decides planning,
|
|
48
|
+
* priority, curation, roadmap, canon and releases, which is most of the bus. An
|
|
49
|
+
* allowlist over a wide grant would have to enumerate nearly every tool, and the
|
|
50
|
+
* one someone forgets would refuse legitimate work in the middle of an absence —
|
|
51
|
+
* failing in the direction that leaves the fleet stuck with nobody to ask.
|
|
52
|
+
*
|
|
53
|
+
* What is refused is small, closed, and named for its reason: the aide's
|
|
54
|
+
* `never merges / never gates` rule. That rule does not widen when David leaves;
|
|
55
|
+
* it is load-bearing precisely because the gate must stay non-author.
|
|
56
|
+
*/
|
|
57
|
+
export const LEAD_REFUSED = {
|
|
58
|
+
merge: "merging is the gate acting. The aide never gates, and leading in David's absence does not change who may — a lead that merges is an author gating its own plan.",
|
|
59
|
+
land: "`land` writes docs/DONE.md, which is the coordinator's file. Leading does not transfer file ownership; ask the coordinator to record it.",
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Decisions that WAIT for David. Not refused because they are dangerous to
|
|
63
|
+
* execute — refused because they are not the aide's to make, and an absence is
|
|
64
|
+
* not a transfer of that authority.
|
|
65
|
+
*/
|
|
66
|
+
export const PARKED_CATEGORIES = [
|
|
67
|
+
"licence",
|
|
68
|
+
"funding",
|
|
69
|
+
"public-vs-private",
|
|
70
|
+
"credentials",
|
|
71
|
+
"anything altering scope or authority",
|
|
72
|
+
"destructive machine actions",
|
|
73
|
+
];
|
|
74
|
+
export function readAway() {
|
|
75
|
+
const f = awayFile();
|
|
76
|
+
if (!existsSync(f))
|
|
77
|
+
return {};
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(readFileSync(f, "utf8"));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function writeAway(state) {
|
|
86
|
+
mkdirSync(ROOT, { recursive: true });
|
|
87
|
+
writeFileSync(awayFile(), `${JSON.stringify(state, null, 2)}\n`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The refusal the LEAD gets, or null if the call is allowed.
|
|
91
|
+
*
|
|
92
|
+
* Only the lead is constrained, and only on the two verbs above. Everyone
|
|
93
|
+
* else's lane is untouched: `coord_away` is not a freeze on the project, and a
|
|
94
|
+
* worker whose tools stopped working because David went out would simply stop
|
|
95
|
+
* calling them.
|
|
96
|
+
*/
|
|
97
|
+
export function awayRefusal(state, agentId, tool) {
|
|
98
|
+
if (!agentId)
|
|
99
|
+
return null;
|
|
100
|
+
const held = Object.values(state).find((s) => s.on && s.leadId === agentId);
|
|
101
|
+
if (!held)
|
|
102
|
+
return null;
|
|
103
|
+
const why = LEAD_REFUSED[tool];
|
|
104
|
+
if (!why)
|
|
105
|
+
return null;
|
|
106
|
+
return (`'${tool}' is refused while you LEAD on '${held.project}' in David's absence: ${why} ` +
|
|
107
|
+
`Leading grants DECISION authority — planning, priority, curation, roadmap, canon, releases — not execution authority. ` +
|
|
108
|
+
`If this genuinely needs doing, it needs the coordinator, or David back. Record it in ${held.decisionLog} either way.`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* RETIRED WITH THE OLD MEANING. Kept as a no-op so the call site in
|
|
112
|
+
* `registerTool` keeps compiling and the retirement is visible where the guard
|
|
113
|
+
* used to be, rather than as an absence someone re-derives later.
|
|
114
|
+
*
|
|
115
|
+
* Under the old meaning the coordinator was the one away, so a second
|
|
116
|
+
* coordinator joining was a fleet-shape change made behind its back. Under this
|
|
117
|
+
* one the coordinator is present and working, and blocking a second coordinator
|
|
118
|
+
* while DAVID is out would refuse a legitimate handoff at exactly the moment
|
|
119
|
+
* nobody can approve it.
|
|
120
|
+
*
|
|
121
|
+
* RECORDED GAP, deliberately not solved here: retiring the duty-officer meaning
|
|
122
|
+
* leaves COORDINATOR absence uncovered. If the coordinator goes down while David
|
|
123
|
+
* is away, nothing covers the seat. That is a decision, not an oversight — it is
|
|
124
|
+
* named so the next reader finds a choice rather than a hole.
|
|
125
|
+
*/
|
|
126
|
+
export function secondCoordinatorRefusal(_state, _agentId, _roleId) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
export const coordAwaySchema = {
|
|
130
|
+
project: z.string().min(1),
|
|
131
|
+
// Omitted → a QUERY: report current state and mutate nothing (q-1abbbdbf).
|
|
132
|
+
// `/coord-away` used to be a bare toggle read by a human deciding which
|
|
133
|
+
// direction to call it — the incident this fixes is David typing it
|
|
134
|
+
// meaning ARM while it was already armed, and the aide independently
|
|
135
|
+
// reading the same typed command as RELEASE, with no state visible to
|
|
136
|
+
// either of them before acting. State-it-aloud-first only works if there
|
|
137
|
+
// is a state to read that does not itself flip the switch.
|
|
138
|
+
on: z.boolean().optional(),
|
|
139
|
+
/** The agent leading in David's absence — the aide. */
|
|
140
|
+
leadId: z.string().optional(),
|
|
141
|
+
/** Repo-relative path where decisions taken during the absence are written. */
|
|
142
|
+
decisionLog: z.string().optional(),
|
|
143
|
+
until: z.string().optional(),
|
|
144
|
+
/** Repo to measure stall coverage against. */
|
|
145
|
+
repo: z.string().optional(),
|
|
146
|
+
/** Turn it on knowing the fleet is unobserved. Recorded in the state. */
|
|
147
|
+
acknowledgeBlindFleet: z.boolean().optional(),
|
|
148
|
+
/** Who is calling — the lead may turn it on and release it. */
|
|
149
|
+
agentId: z.string().optional(),
|
|
150
|
+
};
|
|
151
|
+
async function measureCoverage(repo) {
|
|
152
|
+
if (!repo)
|
|
153
|
+
return null;
|
|
154
|
+
try {
|
|
155
|
+
const r = (await stallCheckTool({ repo }));
|
|
156
|
+
if (!r?.ok)
|
|
157
|
+
return null;
|
|
158
|
+
return { checked: r.checked ?? 0, measurable: r.measurable ?? 0, blind: r.blind ?? [], at: new Date().toISOString() };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function coordAwayTool(args) {
|
|
165
|
+
const state = readAway();
|
|
166
|
+
const prior = state[args.project];
|
|
167
|
+
// QUERY — `on` omitted. Reads, never writes: no coverage measurement, no
|
|
168
|
+
// refusal, no side effect. This is the call a card makes to state current
|
|
169
|
+
// status aloud BEFORE deciding which direction to arm/release, so the
|
|
170
|
+
// decision is never made blind the way it was on 2026-08-30/31.
|
|
171
|
+
if (args.on === undefined) {
|
|
172
|
+
if (!prior?.on) {
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
query: true,
|
|
176
|
+
project: args.project,
|
|
177
|
+
armed: false,
|
|
178
|
+
state: prior ?? null,
|
|
179
|
+
summary: prior
|
|
180
|
+
? `'${args.project}' is NOT armed — last released ${prior.at}.`
|
|
181
|
+
: `'${args.project}' is NOT armed — never armed.`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
query: true,
|
|
187
|
+
project: args.project,
|
|
188
|
+
armed: true,
|
|
189
|
+
state: prior,
|
|
190
|
+
summary: `'${args.project}' is ARMED — '${prior.leadId}' leads since ${prior.at}` +
|
|
191
|
+
`${prior.until ? ` until ${prior.until}` : ""}. ` +
|
|
192
|
+
`Coverage ${prior.coverage.measurable}/${prior.coverage.checked}` +
|
|
193
|
+
`${prior.blindAcknowledged ? " (ACKNOWLEDGED BLIND)" : ""}. ` +
|
|
194
|
+
`Decisions logged to ${prior.decisionLog}.`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (args.on) {
|
|
198
|
+
// A LEAD WITH NO NAME IS AN ABSENCE DESCRIBED AS COVERED, which is strictly
|
|
199
|
+
// worse than an absence everyone can see is uncovered.
|
|
200
|
+
if (!args.leadId) {
|
|
201
|
+
return { ok: false, error: `coord-away ON requires a leadId — the agent leading in David's absence. Turning it on without naming one reports the fleet as led while leaving it unled.` };
|
|
202
|
+
}
|
|
203
|
+
if (!args.decisionLog) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: `coord-away ON requires a decisionLog path. The point of leading through an absence is that the absence ENDS: ` +
|
|
207
|
+
`David should return to a diff he can read, not a transcript he has to reconstruct. A log promised at the start ` +
|
|
208
|
+
`and written at the end is a log nobody wrote.`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (prior?.on) {
|
|
212
|
+
return { ok: false, error: `coord-away is already ON for '${args.project}' — '${prior.leadId}' leads since ${prior.at}. Release it before re-arming.` };
|
|
213
|
+
}
|
|
214
|
+
// THE PRECONDITION: what could the clock actually SEE?
|
|
215
|
+
const coverage = await measureCoverage(args.repo);
|
|
216
|
+
if (!coverage) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: `cannot measure stall coverage for '${args.project}'${args.repo ? "" : " — pass 'repo'"}. ` +
|
|
220
|
+
`Going away is a promise that the fleet is watched while nobody is watching, and an UNMEASURED promise ` +
|
|
221
|
+
`is the one this verb exists to stop making. Fix the measurement, or pass acknowledgeBlindFleet:true to ` +
|
|
222
|
+
`record that the promise is being made anyway.`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// `checked > 0` MATTERS: an empty board is an IDLE fleet, not a blind one.
|
|
226
|
+
// Without it, arming while nothing is in flight refuses with "the clock
|
|
227
|
+
// covers 0 of 0" — a refusal that reads as a fleet nobody can see when in
|
|
228
|
+
// fact there is nothing to see, and the only way past it would be to
|
|
229
|
+
// acknowledge a blindness that does not exist.
|
|
230
|
+
if (coverage.checked > 0 && coverage.measurable === 0 && !args.acknowledgeBlindFleet) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
error: `REFUSING: the stall clock covers ${coverage.measurable} of ${coverage.checked} in-flight row(s). ` +
|
|
234
|
+
`It would RUN and see nothing${coverage.blind.length ? ` — blind on: ${coverage.blind.join(", ")}` : ""}. ` +
|
|
235
|
+
`"The clock ran" and "the fleet is observed" are different facts, and this verb must not treat the first as the second. ` +
|
|
236
|
+
`The usual cause is a board 'Branch · Worktree' cell holding a PATH rather than a branch ref: a path resolves for ` +
|
|
237
|
+
`git and measures the wrong thing, so the check reports it unmeasurable rather than guessing. Fix those cells and ` +
|
|
238
|
+
`coverage returns. If you mean to go anyway, pass acknowledgeBlindFleet:true — it is recorded in the state and in the announcement.`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
state[args.project] = {
|
|
242
|
+
on: true,
|
|
243
|
+
project: args.project,
|
|
244
|
+
away: "david",
|
|
245
|
+
leadId: args.leadId,
|
|
246
|
+
until: args.until,
|
|
247
|
+
at: new Date().toISOString(),
|
|
248
|
+
decisionLog: args.decisionLog,
|
|
249
|
+
coverage,
|
|
250
|
+
...(coverage.checked > 0 && coverage.measurable === 0 ? { blindAcknowledged: true } : {}),
|
|
251
|
+
};
|
|
252
|
+
writeAway(state);
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
state: state[args.project],
|
|
256
|
+
refused: LEAD_REFUSED,
|
|
257
|
+
parked: PARKED_CATEGORIES,
|
|
258
|
+
// A confirmation naming the transition, not just the imperative
|
|
259
|
+
// direction (q-1abbbdbf) — "ON" and "already ON" read the same at a
|
|
260
|
+
// glance; "OFF → ARMED" cannot be misread as a no-op or a repeat.
|
|
261
|
+
transition: "OFF → ARMED",
|
|
262
|
+
announce: `OFF → ARMED. AGENT_ACTION: coord-away ON — David is away${args.until ? ` until ${args.until}` : ""}; '${args.leadId}' LEADS. ` +
|
|
263
|
+
`Decides: planning · priority · curation · roadmap · canon · releases under standing authorisation. ` +
|
|
264
|
+
`Never: merges, gates, or takes a code lane. Parks for David: ${PARKED_CATEGORIES.join(" · ")}. ` +
|
|
265
|
+
`Decisions logged to ${args.decisionLog}. ` +
|
|
266
|
+
`Stall coverage ${coverage.measurable}/${coverage.checked}${coverage.checked > 0 && coverage.measurable === 0 ? " — ACKNOWLEDGED BLIND: the clock runs and sees nothing" : ""}.`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (!prior?.on)
|
|
270
|
+
return { ok: false, error: `coord-away is not on for '${args.project}' — nothing to release.` };
|
|
271
|
+
// RELEASE IS OBSERVING THAT DAVID IS BACK, not lifting a limit on yourself —
|
|
272
|
+
// which is why the lead may do it and a duty officer could not. The parked
|
|
273
|
+
// decisions are NOT unparked by it: they were never the lead's to make, and
|
|
274
|
+
// release does not retroactively authorise one.
|
|
275
|
+
state[args.project] = { ...prior, on: false, at: new Date().toISOString() };
|
|
276
|
+
writeAway(state);
|
|
277
|
+
return {
|
|
278
|
+
ok: true,
|
|
279
|
+
state: state[args.project],
|
|
280
|
+
transition: "ARMED → RELEASED",
|
|
281
|
+
announce: `ARMED → RELEASED. AGENT_ACTION: coord-away RELEASED on '${args.project}' — David is back; '${prior.leadId}' stands down as lead. ` +
|
|
282
|
+
`Anything parked during the absence is still parked: release does not authorise it. Decisions taken: ${prior.decisionLog}.`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=away.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"away.js","sourceRoot":"","sources":["../../src/tools/away.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* IS THIS BOARD CELL'S REF A PER-AGENT ACTIVITY SIGNAL?
|
|
3
|
+
*
|
|
4
|
+
* One classifier, used by the CHECKER that reads the cell and the VERB that
|
|
5
|
+
* writes it. A rule enforced at the read and not the write is a rule the write
|
|
6
|
+
* defeats: `claim` writes these rows, so if a bad ref is refusable by
|
|
7
|
+
* `stall_check` and writable by `claim`, the verb the fleet is being pushed
|
|
8
|
+
* toward becomes the thing that manufactures the bad row.
|
|
9
|
+
*
|
|
10
|
+
* FOUR WAYS THIS ONE CELL HAS BEEN WRONG, all measured on this fleet:
|
|
11
|
+
*
|
|
12
|
+
* `docs/*` a PATH — refused today, correctly
|
|
13
|
+
* `main` a SHARED ref — resolved, and reported the
|
|
14
|
+
* whole fleet's activity as one agent's, so the
|
|
15
|
+
* row could never stall
|
|
16
|
+
* `worker-3/task13-…` LOCAL-ONLY — resolved for its author and for
|
|
17
|
+
* nobody else, so the same board read
|
|
18
|
+
* differently from two checkouts
|
|
19
|
+
* `worker-1/task16-3-…` MERGED — resolves everywhere and is FROZEN by
|
|
20
|
+
* construction, so it reports an ever-growing
|
|
21
|
+
* stall on a lane that is finished
|
|
22
|
+
*
|
|
23
|
+
* THREE OF THOSE FOUR RESOLVED CLEANLY. So the test cannot be "does it
|
|
24
|
+
* resolve" — that check passed on every bad case except the path. The property
|
|
25
|
+
* is narrower and it is the one thing all four violate:
|
|
26
|
+
*
|
|
27
|
+
* THE CELL MUST NAME A REF WHOSE MOVEMENT IS THIS AGENT'S WORK.
|
|
28
|
+
*
|
|
29
|
+
* Which decomposes into three questions git can actually answer:
|
|
30
|
+
*
|
|
31
|
+
* scoped does the ref name begin with this agent's id? That is the ONLY
|
|
32
|
+
* per-agent signal this repo carries. Author cannot serve: measured
|
|
33
|
+
* on 60 commits of `origin/main`, every one is authored "David
|
|
34
|
+
* Balzan", so an `--author` filter would match every agent on every
|
|
35
|
+
* ref forever WHILE LOOKING SCOPED.
|
|
36
|
+
* shared is it resolvable from ANY checkout, not just the one that happens
|
|
37
|
+
* to have fetched it? That means the remote-tracking form.
|
|
38
|
+
* live is it still ahead of the base? A merged branch's ref never moves
|
|
39
|
+
* again, so measuring it produces a number that only grows.
|
|
40
|
+
*
|
|
41
|
+
* WHAT THIS DELIBERATELY DOES NOT DO: guess. Every rejection is reported with
|
|
42
|
+
* the reason and the population it judged, and an UNPUSHED branch is called
|
|
43
|
+
* unpushed rather than stalled — a freshly claimed lane has no shared evidence
|
|
44
|
+
* yet, and saying "stalled" there would be the manufactured-narrowing error
|
|
45
|
+
* pointed at a new lane.
|
|
46
|
+
*/
|
|
47
|
+
import { execFileSync } from "node:child_process";
|
|
48
|
+
const git = (repo, args) => {
|
|
49
|
+
try {
|
|
50
|
+
return execFileSync("git", args, { cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const resolves = (repo, ref) => !!git(repo, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
57
|
+
// Task 5.3/22.1's confirmation half: `docs/*` — a real historical cell value
|
|
58
|
+
// — is a GLOB, and `git check-ref-format` refuses it as a branch name (a `*`
|
|
59
|
+
// is not legal in a ref). A plain path like `docs/QUEUE.md` is, frustratingly,
|
|
60
|
+
// ALSO a syntactically valid ref name (slashes and dots are both legal), so
|
|
61
|
+
// this catches the glob shape precisely and does not claim to catch every
|
|
62
|
+
// path — an honest partial fix is the property this task's own title asks
|
|
63
|
+
// for ("every classifier can say I cannot tell"), not a heuristic that
|
|
64
|
+
// pretends to be exhaustive.
|
|
65
|
+
// Pure syntax check — never needs to run INSIDE a repo, so no `cwd` (unlike
|
|
66
|
+
// every other helper here, which resolves refs against `repo`'s history).
|
|
67
|
+
const isSyntacticallyValidRef = (name) => {
|
|
68
|
+
try {
|
|
69
|
+
execFileSync("git", ["check-ref-format", "--branch", name], { stdio: ["ignore", "pipe", "ignore"] });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/** The `\`ref\`` inside a `Branch · Worktree` cell, or "". */
|
|
77
|
+
export function refInCell(cell) {
|
|
78
|
+
return (String(cell ?? "").match(/`([^`]+)`/)?.[1] ?? "").trim();
|
|
79
|
+
}
|
|
80
|
+
/** The remote-tracking form a board cell should name for `branch`. */
|
|
81
|
+
export const boardRefFor = (branch) => (branch.startsWith("origin/") ? branch : `origin/${branch}`);
|
|
82
|
+
/**
|
|
83
|
+
* Classify one cell for one agent.
|
|
84
|
+
*
|
|
85
|
+
* `base` is the ref a merged branch would be contained in — `origin/main` by
|
|
86
|
+
* convention, passed rather than assumed so a consumer fleet on another default
|
|
87
|
+
* branch is not silently misjudged.
|
|
88
|
+
*/
|
|
89
|
+
export function classifyBoardRef(repo, agentId, cell, base = "origin/main") {
|
|
90
|
+
const raw = refInCell(cell);
|
|
91
|
+
if (!raw)
|
|
92
|
+
return { kind: "empty", why: "no ref in the Branch · Worktree cell" };
|
|
93
|
+
const bare = raw.replace(/^origin\//, "");
|
|
94
|
+
// Task 5.3/22.1: CHECKED FIRST AND UNCONDITIONALLY — the original defect
|
|
95
|
+
// (`docs/*`) was previously only caught in a repo with NO origin remote at
|
|
96
|
+
// all (further down); confirmed live that in a repo WITH an origin — the
|
|
97
|
+
// normal case for every worktree this fleet actually runs — the same
|
|
98
|
+
// value fell through to the "unpushed" branch instead and was reported as
|
|
99
|
+
// "the branch has not been pushed", which is a confidently WRONG diagnosis
|
|
100
|
+
// for a path. `git check-ref-format` answers "could this even syntactically
|
|
101
|
+
// be a branch name" without touching the repo's history at all, so a glob
|
|
102
|
+
// is caught regardless of what else is true about this checkout.
|
|
103
|
+
if (!isSyntacticallyValidRef(bare)) {
|
|
104
|
+
return { kind: "path", why: `'${raw}' is not a syntactically valid branch name — it is a path or glob` };
|
|
105
|
+
}
|
|
106
|
+
// A SHARED REF FIRST, because `main` resolves and would otherwise be measured.
|
|
107
|
+
// Named explicitly rather than inferred from scoping, so the reason a reader
|
|
108
|
+
// gets is the real one.
|
|
109
|
+
const shared = new Set(["main", "master", "develop", "trunk", "HEAD"]);
|
|
110
|
+
if (shared.has(bare)) {
|
|
111
|
+
return {
|
|
112
|
+
kind: "shared",
|
|
113
|
+
why: `'${raw}' is a SHARED branch — every agent merges into it, so its movement is the fleet's activity and not '${agentId}'s. ` +
|
|
114
|
+
`A row pointing here can never stall, which is a manufactured green rather than a measurement.`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// SCOPED BY NAME — but only refusing a ref that belongs to a DIFFERENT agent,
|
|
118
|
+
// never one that is merely unconventional.
|
|
119
|
+
//
|
|
120
|
+
// Authorship cannot carry ownership here (every commit in this repo is
|
|
121
|
+
// authored by the same person, so `--author` would match every agent on every
|
|
122
|
+
// ref while looking scoped), which leaves the branch-naming convention
|
|
123
|
+
// `<agentId>/<slice>`. But REQUIRING that convention would make a repo that
|
|
124
|
+
// does not use it universally blind — and a narrowing that disables the check
|
|
125
|
+
// everywhere is worse than the defect it fixes. So a plain `feature-x` is
|
|
126
|
+
// allowed and `<someone-else>/x` is refused: the second is positively
|
|
127
|
+
// someone else's work, the first is only unlabelled.
|
|
128
|
+
const owner = bare.includes("/") ? bare.slice(0, bare.indexOf("/")) : null;
|
|
129
|
+
if (owner && owner !== agentId && /^[\w.-]+-(worker|aide|qa|ci|coordinator)(-\d+)?$/.test(owner)) {
|
|
130
|
+
return {
|
|
131
|
+
kind: "unscoped",
|
|
132
|
+
why: `'${raw}' is scoped to '${owner}', not '${agentId}' — its movement is that agent's work. ` +
|
|
133
|
+
`A per-agent signal needs a ref only this agent writes, and the branch name is the only ownership marker this repo carries.`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const remote = `origin/${bare}`;
|
|
137
|
+
const remoteResolves = resolves(repo, remote);
|
|
138
|
+
const localResolves = resolves(repo, bare);
|
|
139
|
+
// NO SHARED FORM CAN EXIST, so the local ref is the best evidence there is.
|
|
140
|
+
// Without this a repo with no `origin` remote would report every row
|
|
141
|
+
// unmeasurable forever — the check disabled everywhere by a rule meant to
|
|
142
|
+
// make it honest. Preferring the shared form is right; requiring one that
|
|
143
|
+
// cannot exist is not.
|
|
144
|
+
if (!remoteResolves && !resolves(repo, "origin/HEAD") && git(repo, ["remote", "get-url", "origin"]) === null) {
|
|
145
|
+
if (!localResolves) {
|
|
146
|
+
return { kind: "path", why: `'${raw}' does not resolve as a git ref in a repo with no remote — it is a path or glob` };
|
|
147
|
+
}
|
|
148
|
+
return { kind: "measurable", ref: bare };
|
|
149
|
+
}
|
|
150
|
+
if (!remoteResolves) {
|
|
151
|
+
// Local-only vs never-pushed are different facts and only one is a defect
|
|
152
|
+
// in the CELL. Both are unmeasurable, and neither is a stall.
|
|
153
|
+
if (localResolves) {
|
|
154
|
+
return {
|
|
155
|
+
kind: "local-only",
|
|
156
|
+
why: `'${raw}' resolves in THIS checkout and not on the remote, so the same board reads differently from another checkout — ` +
|
|
157
|
+
`whether it is measurable depends on who last ran \`git fetch\`, which is a property of the reader rather than of the work.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Task 5.3/22.2's fourth cause: "a pushed branch absent from this
|
|
161
|
+
// checkout" is NOT the same fact as "never pushed", and this check has
|
|
162
|
+
// no way to tell them apart without a network call it deliberately does
|
|
163
|
+
// not make (every other verdict here is a local git op only — adding
|
|
164
|
+
// one here would trade the offline property for one more cause). SAYING
|
|
165
|
+
// SO is the fix Task 22's own title asks for: a classifier that names
|
|
166
|
+
// an ABSENCE of evidence as a specific, confident cause is the exact
|
|
167
|
+
// shape "it is a path or glob" was wrong in — just moved one branch
|
|
168
|
+
// over. `'${remote}' does not exist` is the one fact this DOES know;
|
|
169
|
+
// everything past it is now framed as what it cannot distinguish rather
|
|
170
|
+
// than a guess dressed as a finding.
|
|
171
|
+
return {
|
|
172
|
+
kind: "unpushed",
|
|
173
|
+
why: `'${remote}' does not resolve in THIS checkout — either the branch has never been pushed, or it WAS pushed and this ` +
|
|
174
|
+
`checkout's remote-tracking ref is simply stale (nobody has run \`git fetch\` here since). Cannot distinguish the two ` +
|
|
175
|
+
`without asking the remote, which this check deliberately does not do. Either way there is no LOCAL evidence of ` +
|
|
176
|
+
`activity, so it is not scored as a stall.`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// MERGED IS FROZEN. Its ref can never move again, so any age computed from it
|
|
180
|
+
// only grows — an ever-worsening stall on a lane that is finished.
|
|
181
|
+
//
|
|
182
|
+
// `--is-ancestor` ALONE CANNOT SEE IT, and this fleet is the case that proves
|
|
183
|
+
// it: we SQUASH-merge, so the branch's commits never become ancestors of the
|
|
184
|
+
// base. Measured on a branch I knew was merged — `--is-ancestor` said NO
|
|
185
|
+
// while the work was plainly on main. A merged-detector that cannot detect
|
|
186
|
+
// this repo's own merge strategy is inert, which is worse than absent because
|
|
187
|
+
// it reads as covered.
|
|
188
|
+
//
|
|
189
|
+
// `git cherry` compares PATCH IDS, so a squashed single-commit branch reports
|
|
190
|
+
// `-` (already upstream). It is not complete either — squashing several
|
|
191
|
+
// commits into one changes the patch id — so BOTH tests run and either one
|
|
192
|
+
// answering yes is enough. Neither is asked to be sufficient alone.
|
|
193
|
+
if (resolves(repo, base)) {
|
|
194
|
+
const ancestor = git(repo, ["merge-base", "--is-ancestor", remote, base]) !== null;
|
|
195
|
+
const cherry = git(repo, ["cherry", base, remote]);
|
|
196
|
+
const allUpstream = cherry !== null && cherry.length > 0 && cherry.split("\n").every((l) => l.trim().startsWith("-"));
|
|
197
|
+
if (ancestor || allUpstream) {
|
|
198
|
+
return {
|
|
199
|
+
kind: "merged",
|
|
200
|
+
why: `'${remote}' has already landed in ${base} (${ancestor ? "an ancestor" : "every commit's patch is upstream — a squash merge"}) — ` +
|
|
201
|
+
`so this ref is FROZEN and can never move again. ` +
|
|
202
|
+
`Measuring it reports a stall that grows forever on a finished lane. The row needs the agent's current slice, or an idle marker.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { kind: "measurable", ref: remote };
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=board-ref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-ref.js","sourceRoot":"","sources":["../../src/tools/board-ref.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* THE KIND REGISTRY IS THE SINGLE SOURCE, AND THAT IS TASK 9.2.
|
|
3
|
+
*
|
|
4
|
+
* `SubKind` used to be a hand-written union next to a hand-written zod enum next
|
|
5
|
+
* to an emitter that covered one of the four. Nothing connected them, so a kind
|
|
6
|
+
* could be offered for subscription while nothing could ever emit it — and
|
|
7
|
+
* `list_subscriptions` would report its permanent `never evaluated` forever,
|
|
8
|
+
* honestly and uselessly.
|
|
9
|
+
*
|
|
10
|
+
* Now the union, the wire enum, and the emitter set are all derived from THIS
|
|
11
|
+
* object. A kind cannot be offered without an emitter because the enum is
|
|
12
|
+
* generated from the emitters; `test/record-events.test.mjs` closes the other
|
|
13
|
+
* half by asserting each kind actually fires. Unsatisfiable BY CONSTRUCTION,
|
|
14
|
+
* rather than by a reviewer noticing.
|
|
15
|
+
*/
|
|
16
|
+
export const EVENT_KINDS = {
|
|
17
|
+
item: {
|
|
18
|
+
record: "docs/QUEUE.md + docs/DONE.md",
|
|
19
|
+
what: "a queue item closed — it left QUEUE.md and a DONE.md entry appeared in the same commit",
|
|
20
|
+
targetIs: "the queue item id",
|
|
21
|
+
},
|
|
22
|
+
pr: {
|
|
23
|
+
record: "docs/DONE.md",
|
|
24
|
+
what: "a PR recorded in the completion log",
|
|
25
|
+
targetIs: "the PR ref, e.g. owner/repo#163",
|
|
26
|
+
},
|
|
27
|
+
task: {
|
|
28
|
+
record: "docs/phases/**/PHASE*_TASKS.md",
|
|
29
|
+
what: "a phase task checkbox newly ticked",
|
|
30
|
+
targetIs: "the task key, e.g. 5:12.1",
|
|
31
|
+
},
|
|
32
|
+
phase: {
|
|
33
|
+
record: "docs/phases/**/PHASE*_TASKS.md",
|
|
34
|
+
what: "the last open checkbox in a phase document ticked",
|
|
35
|
+
targetIs: "the phase number, e.g. 5",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const EVENT_KIND_IDS = Object.keys(EVENT_KINDS);
|
|
39
|
+
//# sourceMappingURL=event-kinds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-kinds.js","sourceRoot":"","sources":["../../src/tools/event-kinds.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE;QACJ,MAAM,EAAE,8BAA8B;QACtC,IAAI,EAAE,wFAAwF;QAC9F,QAAQ,EAAE,mBAAmB;KAC9B;IACD,EAAE,EAAE;QACF,MAAM,EAAE,cAAc;QACtB,IAAI,EAAE,qCAAqC;QAC3C,QAAQ,EAAE,iCAAiC;KAC5C;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,gCAAgC;QACxC,IAAI,EAAE,oCAAoC;QAC1C,QAAQ,EAAE,2BAA2B;KACtC;IACD,KAAK,EAAE;QACL,MAAM,EAAE,gCAAgC;QACxC,IAAI,EAAE,mDAAmD;QACzD,QAAQ,EAAE,0BAA0B;KACrC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAA4B,CAAC"}
|