Skip to content

Commit fcdf6c5

Browse files
Jammy2211Jammy2211claude
authored
feat(hygiene): perf aggregates all Heart dev-loop timing legs (#94)
Generalizes the perf Heart-consult from import_time only to the whole hygiene-perf timing family: import_time + unit_test_timing + workspace_testmode_timing, each read only when its ~/.pyauto-heart/<leg>.json sidecar exists, aggregated into one regression count (red+yellow across present legs). Falls back to the one-shot subprocess import timing when no leg has a reading. This means new legs (4a #63, 4b) light up in perf with no further conductor change. Tests: existing import_time consult test updated for the aggregated format; new multi-leg aggregation test. PyAutoBrain 38 pass. Claude-Session: https://claude.ai/code/session_01Tw3EwV55k6VzxorYng3Kfn Co-authored-by: Jammy2211 <JNightingale2211@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1c4587a commit fcdf6c5

3 files changed

Lines changed: 38 additions & 24 deletions

File tree

agents/conductors/hygiene/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ kinds, which is what makes its count comparable (or not):
3232

3333
| Mode | Pre-scan (kind) | Delegates to |
3434
|------|-----------------|--------------|
35-
| `perf` | import cost — prefers Heart's tracked **`import_time`** leg when present, else times `import <pkg>` per library in a **subprocess** (**timing**); heavy test/script timing is read from Heart's `script_timing` / `test_run` | `/refactor` / `/bug` (+ Heart timing legs) |
35+
| `perf` | dev-loop timing — prefers Heart's tracked timing legs when present (`import_time`, `unit_test_timing`, `workspace_testmode_timing`), else times `import <pkg>` per library in a **subprocess** (**timing**) | `/refactor` / `/bug` (+ Heart timing legs) |
3636
| `tidy` | git debris — stale branches, stashes, `[gone]` refs, dirty checkouts (**debris**) | `/repo_cleanup` (Brain) |
3737
| `noise` | none — needs a pytest + workspace-script run (**advisory**) | `/cli_noise_clean` (Heart) |
3838
| `deps` | capped/pinned specifiers in library `pyproject.toml` (**surface**) | `/dep_audit` (Heart, hits PyPI) |

agents/conductors/hygiene/hygiene.sh

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -128,32 +128,37 @@ prescan_noise() {
128128
echo "-1|no cheap local signal — runs pytest + workspace scripts (PYAUTO_TEST_MODE=2)"
129129
}
130130

131-
# perf: prefer PyAutoHeart's tracked `import_time` leg (baseline + regression
132-
# over time — the standing signal) when present; otherwise fall back to a
131+
# perf: prefer PyAutoHeart's tracked dev-loop timing legs (baseline + regression
132+
# over time — the standing signals) when present; otherwise fall back to a
133133
# one-shot import-cost timing, timing `import <pkg>` per library in a SUBPROCESS
134-
# (the conductor never imports the science stack itself). Heavy dev-loop timing
135-
# (slow tests / integration scripts) is already observed by PyAutoHeart's
136-
# script_timing / test_run legs — perf points there and routes.
134+
# (the conductor never imports the science stack itself). The legs are the
135+
# hygiene-perf family shipped alongside the conductor: import_time (import cost),
136+
# unit_test_timing (slow unit tests), workspace_testmode_timing (TEST_MODE
137+
# scripts) — each read only when its sidecar exists.
138+
PERF_HEART_LEGS=(import_time unit_test_timing workspace_testmode_timing)
137139
prescan_perf() {
138-
# --- Heart import_time leg (preferred): the tracked over-time view. --------
139-
local heart_json="${HEART_STATE_DIR:-$HOME/.pyauto-heart}/import_time.json"
140-
if [[ -f "$heart_json" ]]; then
141-
local counts
142-
counts=$(python3 - "$heart_json" <<'PY' 2>/dev/null
140+
# --- Heart timing legs (preferred): the tracked over-time view. ------------
141+
local hs="${HEART_STATE_DIR:-$HOME/.pyauto-heart}" leg jf present=0 total=0 parts="" counts r y
142+
for leg in "${PERF_HEART_LEGS[@]}"; do
143+
jf="$hs/${leg}.json"
144+
[[ -f "$jf" ]] || continue
145+
counts=$(python3 - "$jf" <<'PY' 2>/dev/null
143146
import json, sys
144147
try:
145148
d = json.load(open(sys.argv[1]))
146-
print(int(d.get("red_count", 0)), int(d.get("yellow_count", 0)),
147-
int(d.get("green_count", 0)), int(d.get("packages_measured", 0)))
149+
print(int(d.get("red_count", 0)), int(d.get("yellow_count", 0)))
148150
except Exception:
149151
pass
150152
PY
151153
)
152-
if [[ -n "$counts" ]]; then
153-
local r y g m; read -r r y g m <<< "$counts"
154-
echo "$((r + y))|Heart import_time leg: ${r} regressions / ${y} slow / ${g} within baseline (${m} libs tracked); refresh: python -m heart.checks.import_time"
155-
return
156-
fi
154+
[[ -n "$counts" ]] || continue
155+
read -r r y <<< "$counts"
156+
present=$((present + 1)); total=$((total + r + y))
157+
parts+="${leg}:${r}r/${y}y "
158+
done
159+
if [[ "$present" -gt 0 ]]; then
160+
echo "${total}|Heart timing legs (${present}): ${parts}(regressions = red+yellow); refresh via the leg drivers (python -m heart.checks.<leg>)"
161+
return
157162
fi
158163
# --- Fallback: one-shot subprocess timing (no Heart reading available). -----
159164
local slow=0 measured=0 detail="" pkg rc start end t

tests/test_hygiene_conductor.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,29 @@ def test_perf_advisory_when_nothing_importable(tmp_path):
8181
assert row["status"] == "advisory" and row["count"] is None
8282

8383

84-
def test_perf_prefers_heart_import_time_leg_when_present(tmp_path):
85-
# When Heart's import_time leg has produced a reading, perf surfaces the
84+
def test_perf_prefers_heart_timing_legs_when_present(tmp_path):
85+
# When a Heart dev-loop timing leg has produced a reading, perf surfaces the
8686
# tracked baseline/regression view instead of its own one-shot timing.
8787
heart = tmp_path / "heart"
8888
heart.mkdir()
89-
(heart / "import_time.json").write_text(json.dumps({
90-
"red_count": 1, "yellow_count": 1, "green_count": 3, "packages_measured": 5,
91-
}))
89+
(heart / "import_time.json").write_text(json.dumps({"red_count": 1, "yellow_count": 1}))
9290
r = _run(["perf", "--json"], tmp_path, extra={"HEART_STATE_DIR": str(heart)})
9391
assert r.returncode == 0, r.stderr
9492
row = json.loads(r.stdout)["row"]
9593
assert row["mode"] == "perf" and row["kind"] == "timing"
9694
assert row["count"] == 2 # red + yellow regressions
97-
assert "import_time leg" in row["summary"]
95+
assert "import_time" in row["summary"]
96+
97+
98+
def test_perf_aggregates_multiple_heart_timing_legs(tmp_path):
99+
heart = tmp_path / "heart"
100+
heart.mkdir()
101+
(heart / "import_time.json").write_text(json.dumps({"red_count": 1, "yellow_count": 1}))
102+
(heart / "unit_test_timing.json").write_text(json.dumps({"red_count": 2, "yellow_count": 0}))
103+
r = _run(["perf", "--json"], tmp_path, extra={"HEART_STATE_DIR": str(heart)})
104+
row = json.loads(r.stdout)["row"]
105+
assert row["count"] == 4 # 2 + 2 regressions across both legs
106+
assert "import_time" in row["summary"] and "unit_test_timing" in row["summary"]
98107

99108

100109
def test_unknown_mode_exits_2(tmp_path):

0 commit comments

Comments
 (0)