Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions agents/faculties/vitals/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ faculty every cycle.)
5. **Explain and recommend.** Produce the structured report below. Recommendations
should be actionable and, where Heart offers a remediation entry point, cite it
(`pyauto-heart fix ci <repo>`, `fix dirty <repo>`, `fix drift`,
`fix timing <project>`). Do not invent fixes Heart cannot support.
`fix timing <project>`). For an evidence gap the entry point is
`fix stale`: it prints the check that closes each gap and the one plan that
closes them all β€” quote those, do not compose your own. Do not invent fixes
Heart cannot support.

## Output schema

Expand All @@ -143,7 +146,8 @@ Status: <GREEN | STALE | YELLOW | RED> (score <0-100>, snapshot <ts>)
- <yellow reason, mapped to its capability> (or "None")

### Evidence Gaps
- <stale reason, mapped to its capability, with the check to re-run> (or "None")
- <stale reason, mapped to its capability, with the check to re-run β€” the
command Heart publishes for it, never one you invented> (or "None")

### Recommendations
- <actionable next step, citing a `pyauto-heart fix ...` where applicable> (or "None")
Expand Down
5 changes: 4 additions & 1 deletion agents/faculties/vitals/HEART_CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ reimplement inside Brain.
- `pyauto-heart watch [seconds]` / `live` β€” continuous monitoring daemon.
- `pyauto-heart stop` / `stop --all` β€” daemon lifecycle control.
- `pyauto-heart fix <topic>` β€” emits a context bundle/invocation for remediation;
it must not mutate other repos directly.
it must not mutate other repos directly. `fix stale` is the freshness door:
the current evidence gaps, the check that closes each, and the ONE plan
(prompt, plus a command chain where every gap has one) that closes them all β€”
the same payloads the board's ⌨/πŸ“‹ chips carry.

## Continuous checks in Heart

Expand Down
2 changes: 1 addition & 1 deletion board/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ copy-for-Claude payload:
|---------|--------------|-----------------|
| ⌨ Morning sync | `bin/morning.sh` (local) | the terminal command itself |
| πŸŒ™ Overnight | scheduled workflows (`config/policy.yaml board: overnight_jobs`); a ⏸ blocked gate's ::warning annotation is rendered inline | `/bug … β€” <run url>` on failures |
| ❀️ Readiness & release | the Heart board's `badge.json` + `board.json` (structured blockers, each carrying its OWN `/bug` prompt β€” rendered verbatim, never re-derived) and the Hands badge | `/health`, the blockers' own prompts |
| ❀️ Readiness & release | the Heart board's `badge.json` + `board.json` (structured blockers, each carrying its OWN `/bug` prompt β€” or, for an evidence gap, the `command` that re-runs its check β€” plus `stale_plan`, the one payload that closes every gap; all rendered verbatim, never re-derived) and the Hands badge | `/health`, the plan's prompt or command chain, the blockers' own prompts |
| ⏱ Test performance | the Heart board's published `performance` block β€” rendered verbatim, never re-derived | each row's own prompt |
| 🏷️ Version consistency | the coupled-set stamps (`board: version_stamps`) | `/bug version drift: …` |
| πŸ’¬ Community | the Ears (`community scan`, reused wholesale) β€” every open conversation gets a row | `/community`, `/community triage <ref>` |
Expand Down
36 changes: 36 additions & 0 deletions board/_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,26 @@ def extract_heart_blockers(board):
"repo_url": b.get("repo_url"),
"run_url": b.get("run_url"),
"prompt": b.get("prompt"),
# An evidence gap also arrives with the command that re-runs its check
# (Heart board.json v3); absent on other severities and on an older
# Heart publish. Forwarded verbatim, like everything else here.
"command": b.get("command"),
} for b in blockers[:HEART_BLOCKER_CAP]]


def extract_heart_plan(board):
"""The Heart's whole-tier remedy β€” one payload that closes every current
evidence gap ({count, command, prompt}). Rendered here verbatim; the Brain
never derives a remedy of its own. None when nothing is stale, or when the
surface predates the field."""
plan = (board or {}).get("stale_plan")
if not isinstance(plan, dict) or not plan.get("prompt"):
return None
return {"count": plan.get("count"),
"command": plan.get("command"),
"prompt": str(plan["prompt"])}


def fetch_heart_blockers(pages_base, repo, degraded):
"""Fetch-and-extract in one call (the blockers-only door)."""
return extract_heart_blockers(fetch_heart_board(pages_base, repo, degraded))
Expand Down Expand Up @@ -725,6 +742,7 @@ def collect():
# and the test-performance block.
heart_board = fetch_heart_board(pages_base, heart_repo, degraded)
heart_blockers = extract_heart_blockers(heart_board)
heart_plan = extract_heart_plan(heart_board)
performance = extract_heart_performance(
heart_board, f"{pages_base}/{heart_repo}/")
hands = fetch_badge(pages_base, board_family.get("hands", "PyAutoHands"))
Expand All @@ -743,6 +761,7 @@ def collect():
"overnight": overnight,
"heart": heart,
"heart_blockers": heart_blockers,
"heart_plan": heart_plan,
"performance": performance,
"hands": hands,
"versions": versions,
Expand Down Expand Up @@ -871,12 +890,20 @@ def render_md(data):
f"[board]({data['boards'].get('heart', '')}) Β· re-run via `/health`")
else:
L.append("- Heart board unreachable β€” consult `/health` directly")
plan = data.get("heart_plan")
if plan:
# The gaps are worth one line, not N: the Heart already wrote the plan
# that closes all of them, and `fix stale` prints it in a terminal.
clear = f"`{plan['command']}`" if plan.get("command") else "`pyauto-heart fix stale`"
L.append(f"- Evidence gaps: {plan.get('count', '?')} β€” clear them all: {clear}")
for b in data.get("heart_blockers") or []:
sev = f"[{b['severity']}] " if b.get("severity") else ""
line = f" - {sev}{b['text']}"
if b.get("run_url"):
line += f" β€” [run]({b['run_url']})"
L.append(line)
if b.get("command"):
L.append(f" - `{b['command']}`")
if b.get("prompt"):
L.append(f" - `{b['prompt']}`")
if data.get("hands"):
Expand Down Expand Up @@ -1166,6 +1193,15 @@ def render_html(data):
else:
H.append(_row("Heart board unreachable β€” consult the clinician "
"directly." + pills(("unreachable", "y")), "/health"))
plan = data.get("heart_plan")
if plan:
# One tap for the whole stale tier, above the gaps it closes: the
# Claude prompt always, the shell chain when the Heart could offer one.
H.append(_row(f'Clear all {plan.get("count", "?")} evidence gaps β€” one prompt'
+ pills(("stale", "y")), plan["prompt"]))
if plan.get("command"):
H.append(_row("… or the command chain that re-runs every check",
plan["command"], term=True))
for b in data.get("heart_blockers") or []:
sev = b.get("severity")
links = "".join(
Expand Down
66 changes: 64 additions & 2 deletions tests/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

import base64
import html
import json
import os
import re
Expand All @@ -26,7 +27,8 @@
BRAIN = BRAIN_HOME / "bin" / "pyauto-brain"

SURFACE_KEYS = {
"generated", "org", "overnight", "heart", "heart_blockers", "performance",
"generated", "org", "overnight", "heart", "heart_blockers", "heart_plan",
"performance",
"hands", "versions", "community", "resume", "open_issues", "hygiene",
"devbox", "autonomy", "doors", "boards", "degraded", "history",
}
Expand Down Expand Up @@ -78,14 +80,17 @@
}

HEART_BOARD_JSON = {
"schema_version": 2,
"schema_version": 3,
"blockers": [{
"text": "RepoA: nightly smoke red",
"severity": "red",
"repo": "RepoA",
"repo_url": "https://example.invalid/RepoA",
"run_url": "https://example.invalid/run/9",
"prompt": "/bug Heart board: RepoA nightly smoke red β€” https://example.invalid/run/9",
# v3: a stale row carries the command that re-runs its check; a red
# one has no such remedy β€” the fix is code, not a re-run.
"command": None,
}],
# Additive to schema v2 β€” an older Heart publish simply omits it.
"performance": HEART_PERFORMANCE,
Expand Down Expand Up @@ -529,6 +534,63 @@ def test_heart_blockers_render_with_their_own_prompts(tmp_path):
assert "Shipped: **GREEN**" in md # the Hands headline joined the section


HEART_BOARD_STALE = {
"schema_version": 3,
"blockers": [
{"text": "install verification not run", "severity": "stale",
"repo": None, "repo_url": None, "run_url": None,
"command": "pyauto-heart verify_install --report-json",
"prompt": "/health run `pyauto-heart verify_install --report-json` β€” …"},
{"text": "no release validation for current source", "severity": "stale",
"repo": None, "repo_url": None, "run_url": None, "command": None,
"prompt": "/health dispatch a release rehearsal with `/release rehearse` β€” …"},
],
"stale_plan": {
"count": 2,
"command": None,
"prompt": "/health clear the Heart's 2 evidence gap(s) β€” …\n1. …\n2. …",
},
}


def test_a_stale_heart_offers_the_one_plan_that_clears_every_gap(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(), HEART_BOARD_STALE)
page = _run(["--html"], tmp_path, stub).stdout
plan = HEART_BOARD_STALE["stale_plan"]

# The Heart's own plan is the chip payload β€” the Brain never derives one.
assert html.escape(plan["prompt"], quote=True) in page
assert "Clear all 2 evidence gaps" in page
# Each gap still arrives with the command that closes it, forwarded whole.
s = json.loads(_run(["--json"], tmp_path, stub).stdout)
assert [b["command"] for b in s["heart_blockers"]] == [
"pyauto-heart verify_install --report-json", None]
assert s["heart_plan"]["count"] == 2
# The digest spends one line on the tier, not one per gap.
md = _run([], tmp_path, stub).stdout
assert "Evidence gaps: 2 β€” clear them all: `pyauto-heart fix stale`" in md


def test_a_plan_with_a_command_chain_offers_the_terminal_door(tmp_path):
chain = "pyauto-heart verify_install --report-json && pyauto-heart tick"
board = {**HEART_BOARD_STALE,
"stale_plan": {**HEART_BOARD_STALE["stale_plan"], "command": chain}}
stub = _fabricate(tmp_path, _default_fixtures(), board)

page = _run(["--html"], tmp_path, stub).stdout
assert f'data-cmd="{html.escape(chain, quote=True)}"' in page
assert "copy term" in page # the ⌨ terminal chip, not the πŸ“‹ one
md = _run([], tmp_path, stub).stdout
assert f"clear them all: `{chain}`" in md


def test_a_heart_board_without_a_plan_renders_none(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures()) # no stale_plan published
s = json.loads(_run(["--json"], tmp_path, stub).stdout)
assert s["heart_plan"] is None
assert "Evidence gaps:" not in _run([], tmp_path, stub).stdout


def test_test_performance_rows_carry_their_own_prompts(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(), HEART_BOARD_WITH_EVENT)
s = json.loads(_run(["--json"], tmp_path, stub).stdout)
Expand Down
Loading