Skip to content

Commit 751d5ab

Browse files
Jammy2211claude
andauthored
All doors carries every door — agents and workflow skills alike (#257)
* brain: All doors carries every door — agents AND workflow skills The roster already read every agent live from the dispatcher registry (all 13 conductors + 5 faculties); it now also lists the non-agent doors — compositions, dev-flow entries, ship/cleanup workflows — from their own single source, skills/*/SKILL.md (first sentence of the frontmatter description; dir name is the /verb chip). board (this page) and wake_up (superseded by it) stay off on purpose. 421 tests pass. Co-Authored-By: Claude <noreply@anthropic.com> * brain: board publish idempotence ignores the timestamp The unchanged-observation check compared exact file text, but the payload embeds ts — so a re-publish crossing a second boundary pushed a no-op commit and re-triggered the board (caught by the 3.13 CI leg racing the clock; 3.12 ran inside one second). Compare with ts stripped: an unchanged observation never re-pushes. Test pins ts-insensitivity explicitly. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 183eb6f commit 751d5ab

3 files changed

Lines changed: 61 additions & 9 deletions

File tree

board/_board.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,10 @@ def sparkline(history):
428428

429429

430430
def collect_doors():
431-
"""The conductor/faculty roster, read from the dispatcher registry itself
432-
(bin/pyauto-brain is the single source; never a second copy here)."""
431+
"""EVERY door, from the two single sources: the dispatcher registry
432+
(conductors + faculties — all the agents) and skills/*/SKILL.md (the
433+
non-agent doors: compositions, dev-flow entries, ship/cleanup workflows).
434+
Never a second hand-written roster."""
433435
script = (
434436
f'source "{BRAIN_HOME}/bin/pyauto-brain"; '
435437
'for v in "${CONDUCTOR_ORDER[@]}"; do printf "conductor\\t%s\\t%s\\n" "$v" "${AGENT_DESC[$v]}"; done; '
@@ -445,6 +447,20 @@ def collect_doors():
445447
parts = line.split("\t", 2)
446448
if len(parts) == 3:
447449
doors.append({"tier": parts[0], "verb": parts[1], "desc": parts[2]})
450+
agent_verbs = {d["verb"] for d in doors}
451+
# board (this page) and wake_up (superseded BY this page) stay off the
452+
# roster on purpose.
453+
skip = agent_verbs | {"board", "wake_up"}
454+
for skill in sorted((BRAIN_HOME / "skills").glob("*/SKILL.md")):
455+
verb = skill.parent.name
456+
if verb in skip:
457+
continue
458+
m = re.search(r"^description:\s*(.+)$", skill.read_text(encoding="utf-8"),
459+
re.M)
460+
desc = m.group(1).strip() if m else ""
461+
# First sentence only — the SKILL.md carries the full contract.
462+
desc = re.split(r"(?<=[.!?]) ", desc, maxsplit=1)[0][:140]
463+
doors.append({"tier": "skill", "verb": verb, "desc": desc})
448464
return doors
449465

450466

@@ -996,12 +1012,21 @@ def community_row(e, note_html):
9961012
doors = data["doors"]
9971013
if doors:
9981014
H.append("<h2>🚪 All doors</h2>")
999-
H.append("<details><summary>every conductor and faculty</summary>")
1015+
H.append("<details><summary>every agent and workflow door</summary>")
10001016
for d in doors:
1017+
if d["tier"] == "skill":
1018+
continue
10011019
tier = '<span class="muted"> (faculty)</span>' \
10021020
if d["tier"] == "faculty" else ""
10031021
H.append(_row(f'<b>/{esc(d["verb"])}</b>{tier}{esc(d["desc"])}',
10041022
f"/{d['verb']}"))
1023+
skills = [d for d in doors if d["tier"] == "skill"]
1024+
if skills:
1025+
H.append('<p class="muted">Workflow doors — compositions and '
1026+
'dev-flow entries, no agent of their own:</p>')
1027+
for d in skills:
1028+
H.append(_row(f'<b>/{esc(d["verb"])}</b> — {esc(d["desc"])}',
1029+
f"/{d['verb']}"))
10051030
H.append("</details>")
10061031

10071032
if data["degraded"]:

board/_publish.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,20 @@ def main(argv=None):
160160
return 2
161161

162162
DEVBOX_FILE.parent.mkdir(parents=True, exist_ok=True)
163-
if DEVBOX_FILE.exists() and DEVBOX_FILE.read_text() == text:
164-
print("board publish: devbox observation already current — nothing to push")
165-
return 0
163+
# Idempotence ignores the timestamp: an unchanged observation must not
164+
# bump the file (and re-trigger brain_board.yml) just because the clock
165+
# moved between two runs.
166+
if DEVBOX_FILE.exists():
167+
try:
168+
prev = json.loads(DEVBOX_FILE.read_text())
169+
except (json.JSONDecodeError, OSError):
170+
prev = None
171+
if prev is not None and \
172+
{k: v for k, v in prev.items() if k != "ts"} == \
173+
{k: v for k, v in payload.items() if k != "ts"}:
174+
print("board publish: devbox observation already current — "
175+
"nothing to push")
176+
return 0
166177
DEVBOX_FILE.write_text(text)
167178

168179
rel = os.path.relpath(DEVBOX_FILE, PUBLISH_REPO)

tests/test_board.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,17 @@ def test_json_surface_is_complete_and_derives_org(tmp_path):
245245
assert s["open_issues"] == 42
246246
# Community section reuses the Ears' scan surface wholesale.
247247
assert s["community"]["counts"]["awaiting_response"] == 0
248-
# The doors roster comes from the dispatcher registry, both tiers.
248+
# The doors roster covers every agent (dispatcher registry, both tiers)
249+
# AND every workflow door (skills/ minus the agents).
249250
verbs = {d["verb"] for d in s["doors"]}
250251
assert {"intake", "health", "vitals"} <= verbs
251-
assert "board" not in verbs # surfaces are not agents
252+
skill_verbs = {d["verb"] for d in s["doors"] if d["tier"] == "skill"}
253+
assert {"route", "prm", "start_dev", "issue_cleanup"} <= skill_verbs
254+
for d in s["doors"]:
255+
if d["tier"] == "skill":
256+
assert d["desc"], d["verb"] # frontmatter description parsed
257+
assert "board" not in verbs # the page never lists itself
258+
assert "wake_up" not in verbs # superseded BY this page
252259
# Sibling boards resolved against the pages base.
253260
assert s["boards"]["heart"].endswith("/PyAutoHeart/")
254261

@@ -512,10 +519,19 @@ def test_publish_commits_and_pushes_to_main_only(tmp_path):
512519
assert shown.returncode == 0
513520
assert json.loads(shown.stdout)["worktrees"][0]["repo"] == "RepoA"
514521
assert "hygiene" not in json.loads(shown.stdout) # --no-hygiene
515-
# Re-publishing an identical observation pushes nothing new.
522+
# Re-publishing an identical observation pushes nothing new — and the
523+
# comparison ignores the timestamp by design (an unchanged observation
524+
# must not re-trigger the board just because the clock moved).
516525
r2 = subprocess.run([str(BRAIN), "board", "publish", "--no-hygiene"],
517526
capture_output=True, text=True, env=env, cwd=tmp_path)
518527
assert "nothing to push" in r2.stdout
528+
state_file = brain / "state" / "devbox_board.json"
529+
stored = json.loads(state_file.read_text())
530+
stored["ts"] = "2020-01-01T00:00:00Z"
531+
state_file.write_text(json.dumps(stored, indent=2, sort_keys=True) + "\n")
532+
r2b = subprocess.run([str(BRAIN), "board", "publish", "--no-hygiene"],
533+
capture_output=True, text=True, env=env, cwd=tmp_path)
534+
assert "nothing to push" in r2b.stdout
519535
# Off main, publish refuses (guard against feature-branch commits).
520536
subprocess.run(["git", "-C", str(brain), "checkout", "-qb", "other"],
521537
check=True)

0 commit comments

Comments
 (0)