@rm0nroe/coach-claw 1.0.7 → 1.0.8
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.
|
@@ -383,12 +383,16 @@ def test_celebrate_block_includes_verbatim_instruction(cup, now):
|
|
|
383
383
|
def test_celebrate_combines_all_event_kinds(cup, now):
|
|
384
384
|
"""Regression + streak + graduation + level-up — verify ordering and
|
|
385
385
|
that all four sections are present without bleeding into each other."""
|
|
386
|
+
# Use slug-form ids whose humanized fallback (`-` → space) matches
|
|
387
|
+
# the asserted display text. display_name() is now the single
|
|
388
|
+
# source of truth — marker `name` is ignored, so synthetic ids
|
|
389
|
+
# without an override render via humanized-slug fallback.
|
|
386
390
|
block = cup._assemble_celebrate_block(
|
|
387
|
-
grads=[{"id": "
|
|
391
|
+
grads=[{"id": "pattern-g", "name": "pattern g", "direction": "positive",
|
|
388
392
|
"graduated_reason": "present-5-runs"}],
|
|
389
|
-
regs=[{"id": "
|
|
393
|
+
regs=[{"id": "pattern-r", "name": "pattern r",
|
|
390
394
|
"originally_graduated_at": "2026-04-01"}],
|
|
391
|
-
streak_rewards=[{"id": "
|
|
395
|
+
streak_rewards=[{"id": "pattern-s", "name": "pattern s",
|
|
392
396
|
"direction": "negative", "streak": 2, "target": 5,
|
|
393
397
|
"xp_awarded": 1}],
|
|
394
398
|
levelup={"from": "L3 X", "to": "Y", "to_idx": 3, "xp_at_levelup": 100},
|
|
@@ -146,9 +146,10 @@ def test_regression_terminal_uses_blockquote(cup):
|
|
|
146
146
|
"originally_graduated_at": "2026-04-01"}],
|
|
147
147
|
env="terminal",
|
|
148
148
|
)
|
|
149
|
-
# Verbatim banner:
|
|
150
|
-
#
|
|
151
|
-
|
|
149
|
+
# Verbatim banner: display_name override wins over marker name, so
|
|
150
|
+
# the heading shows the curated wording. Graduation date is
|
|
151
|
+
# interpolated into the body sentence.
|
|
152
|
+
assert "> ⚠️ **Regressed: edits without testing**" in block
|
|
152
153
|
assert "(was graduated 2026-04-01)" in block
|
|
153
154
|
assert "edits-without-testing" not in block # slug must not leak
|
|
154
155
|
assert "---" not in block
|
|
@@ -160,7 +161,7 @@ def test_regression_ide_uses_hr_frame(cup):
|
|
|
160
161
|
"originally_graduated_at": "2026-04-01"}],
|
|
161
162
|
env="ide",
|
|
162
163
|
)
|
|
163
|
-
assert "⚠️ **Regressed** — `
|
|
164
|
+
assert "⚠️ **Regressed** — `edits without testing`" in block
|
|
164
165
|
assert "edits-without-testing" not in block # slug must not leak
|
|
165
166
|
assert block.startswith("---\n")
|
|
166
167
|
assert block.endswith("\n---")
|
|
@@ -292,6 +293,46 @@ def test_graduation_ide_positive(cup):
|
|
|
292
293
|
# attribution — graduation ceremonies get bespoke colors.
|
|
293
294
|
# -----------------------------------------------------------------------------
|
|
294
295
|
|
|
296
|
+
def test_curated_override_wins_over_marker_name(cup):
|
|
297
|
+
"""Regression: when a marker carries a `name` that differs from the
|
|
298
|
+
curated WORDING_OVERRIDES entry for that slug, the override MUST win.
|
|
299
|
+
The teammate-found bug was the milestone renderers preferring marker
|
|
300
|
+
name over display_name's resolution chain — defeating the natural-
|
|
301
|
+
language override contract for known awkward phrases."""
|
|
302
|
+
# `commit-without-testing` carries marker name "commit without
|
|
303
|
+
# testing" (analyze.py:350), but the curated override is the richer
|
|
304
|
+
# "committing without testing".
|
|
305
|
+
streak_block = cup._streak_reward_block(
|
|
306
|
+
[{"id": "commit-without-testing", "name": "commit without testing",
|
|
307
|
+
"direction": "negative", "streak": 3, "target": 5, "xp_awarded": 1}],
|
|
308
|
+
env="terminal",
|
|
309
|
+
)
|
|
310
|
+
assert "committing without testing" in streak_block, (
|
|
311
|
+
"streak reward banner ignored the curated override"
|
|
312
|
+
)
|
|
313
|
+
assert "`commit without testing`" not in streak_block, (
|
|
314
|
+
"marker name leaked through despite override match"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
grad_block = cup._graduation_block(
|
|
318
|
+
[{"id": "commit-without-testing", "name": "commit without testing",
|
|
319
|
+
"direction": "negative", "graduated_reason": "absent-5-runs"}],
|
|
320
|
+
env="terminal",
|
|
321
|
+
)
|
|
322
|
+
assert "GRADUATED: committing without testing" in grad_block, (
|
|
323
|
+
"graduation banner ignored the curated override"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
reg_block = cup._regression_block(
|
|
327
|
+
[{"id": "commit-without-testing", "name": "commit without testing",
|
|
328
|
+
"originally_graduated_at": "2026-04-01"}],
|
|
329
|
+
env="terminal",
|
|
330
|
+
)
|
|
331
|
+
assert "Regressed: committing without testing" in reg_block, (
|
|
332
|
+
"regression banner ignored the curated override"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
295
336
|
def test_graduation_negative_full_bar_is_yellow(cup):
|
|
296
337
|
block = cup._graduation_block(
|
|
297
338
|
[{"id": "edits-without-testing", "name": "edits without testing",
|
|
@@ -574,12 +574,11 @@ def _regression_block(
|
|
|
574
574
|
if not isinstance(r, dict):
|
|
575
575
|
continue
|
|
576
576
|
rid = r.get("id", "?")
|
|
577
|
+
# display_name is the single source of truth: curated override →
|
|
578
|
+
# profile.name → humanized slug. Marker name is intentionally
|
|
579
|
+
# ignored here so a curated WORDING_OVERRIDES entry always wins
|
|
580
|
+
# over whatever wording the marker happened to carry at write time.
|
|
577
581
|
rname = _display_name(rid, profile) if rid != "?" else rid
|
|
578
|
-
# Marker may carry richer wording than display_name resolves;
|
|
579
|
-
# prefer the explicit name field when present and non-slug.
|
|
580
|
-
marker_name = r.get("name")
|
|
581
|
-
if marker_name and marker_name != rid:
|
|
582
|
-
rname = marker_name
|
|
583
582
|
originally_at = r.get("originally_graduated_at", "?")
|
|
584
583
|
sentence = (
|
|
585
584
|
f"Re-detected this run, so it's off the mastered list "
|
|
@@ -608,10 +607,10 @@ def _streak_reward_block(
|
|
|
608
607
|
if not isinstance(r, dict):
|
|
609
608
|
continue
|
|
610
609
|
rid = r.get("id", "?")
|
|
610
|
+
# See _regression_block: display_name is authoritative; the
|
|
611
|
+
# marker's `name` field is intentionally ignored so curated
|
|
612
|
+
# overrides win over whatever the marker carried at write time.
|
|
611
613
|
rname = _display_name(rid, profile) if rid != "?" else rid
|
|
612
|
-
marker_name = r.get("name")
|
|
613
|
-
if marker_name and marker_name != rid:
|
|
614
|
-
rname = marker_name
|
|
615
614
|
streak = int(r.get("streak", 0))
|
|
616
615
|
target = int(r.get("target", 5))
|
|
617
616
|
xp = int(r.get("xp_awarded", 1))
|
|
@@ -652,10 +651,10 @@ def _graduation_block(
|
|
|
652
651
|
if not isinstance(g, dict):
|
|
653
652
|
continue
|
|
654
653
|
gid = g.get("id", "?")
|
|
654
|
+
# See _regression_block: display_name is authoritative; the
|
|
655
|
+
# marker's `name` field is intentionally ignored so curated
|
|
656
|
+
# overrides win over whatever the marker carried at write time.
|
|
655
657
|
gname = _display_name(gid, profile) if gid != "?" else gid
|
|
656
|
-
marker_name = g.get("name")
|
|
657
|
-
if marker_name and marker_name != gid:
|
|
658
|
-
gname = marker_name
|
|
659
658
|
direction = g.get("direction", "negative")
|
|
660
659
|
if direction == "positive":
|
|
661
660
|
sentence = positive_sentence
|
|
@@ -749,21 +748,17 @@ def _assemble_celebrate_block(
|
|
|
749
748
|
graduated_ids = {g.get("id") for g in (grads or []) if isinstance(g, dict) and g.get("id")}
|
|
750
749
|
streak_rewards = [s for s in streak_rewards if s.get("id") not in graduated_ids]
|
|
751
750
|
|
|
752
|
-
# Pass C: normalize each reward's `name` field via display_name so
|
|
753
|
-
# the default-theme and bespoke-theme render paths see
|
|
754
|
-
# wording (override → profile.name → humanized slug).
|
|
755
|
-
#
|
|
756
|
-
#
|
|
757
|
-
#
|
|
758
|
-
# avoid mutating the marker payload.
|
|
751
|
+
# Pass C: normalize each reward's `name` field via display_name so
|
|
752
|
+
# both the default-theme and bespoke-theme render paths see the same
|
|
753
|
+
# user-facing wording (override → profile.name → humanized slug).
|
|
754
|
+
# display_name is authoritative — the marker's own `name` field is
|
|
755
|
+
# ignored so a curated WORDING_OVERRIDES entry always wins over
|
|
756
|
+
# whatever wording the marker carried at write time. We rebuild
|
|
757
|
+
# dicts to avoid mutating the marker payload.
|
|
759
758
|
normalized: list[dict] = []
|
|
760
759
|
for s in streak_rewards:
|
|
761
760
|
sid = s.get("id", "?")
|
|
762
|
-
|
|
763
|
-
if marker_name and marker_name != sid:
|
|
764
|
-
display = marker_name
|
|
765
|
-
else:
|
|
766
|
-
display = _display_name(sid, profile) if sid != "?" else sid
|
|
761
|
+
display = _display_name(sid, profile) if sid != "?" else sid
|
|
767
762
|
normalized.append({**s, "name": display})
|
|
768
763
|
streak_rewards = normalized
|
|
769
764
|
|