generated from coulomb/repo-seed
Implement weekly coding retro schedule
This commit is contained in:
@@ -25,6 +25,7 @@ from activity_core.rules.models import TaskRef, TaskSpec
|
||||
|
||||
_DEFINITIONS_DIR = Path(__file__).parent.parent / "activity-definitions"
|
||||
_SBOM_DEF_PATH = _DEFINITIONS_DIR / "weekly-sbom-staleness.md"
|
||||
_CODING_RETRO_DEF_PATH = _DEFINITIONS_DIR / "weekly-coding-retro.md"
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -95,6 +96,69 @@ def test_sbom_definition_parses_correctly():
|
||||
assert defn.rules[0]["id"] == "flag-stale-sbom"
|
||||
|
||||
|
||||
def test_coding_retro_definition_parses_disabled_until_verified():
|
||||
defn = parse_file(_CODING_RETRO_DEF_PATH)
|
||||
|
||||
assert defn.id == "weekly-coding-retro"
|
||||
assert defn.enabled is False
|
||||
assert defn.trigger_config["trigger_type"] == "cron"
|
||||
assert defn.trigger_config["cron_expression"] == "0 19 * * 6"
|
||||
assert defn.trigger_config["timezone"] == "Europe/Berlin"
|
||||
assert defn.context_sources == [
|
||||
{
|
||||
"type": "state-hub",
|
||||
"query": "coding_retro",
|
||||
"params": {"window_days": 7, "limit": 100},
|
||||
"bind_to": "context.retro",
|
||||
}
|
||||
]
|
||||
assert len(defn.rules) == 1
|
||||
assert defn.rules[0]["id"] == "propose-weekly-improvements"
|
||||
|
||||
|
||||
def test_coding_retro_rule_emits_one_task_per_positive_suggestion():
|
||||
defn = parse_file(_CODING_RETRO_DEF_PATH)
|
||||
rule = defn.rules[0]
|
||||
context = {
|
||||
"retro": {
|
||||
"suggestions": [
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"title": "Harden coding retro smoke gates",
|
||||
"recommendation": "Dry-run with fixture and live hub evidence.",
|
||||
"priority": "high",
|
||||
"score": 8.5,
|
||||
},
|
||||
{
|
||||
"repo": "quiet-repo",
|
||||
"title": "Do not emit zero-score suggestion",
|
||||
"recommendation": "This should stay quiet.",
|
||||
"priority": "low",
|
||||
"score": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
specs = expand_rule_actions([rule], _EmptyEvent(), context)
|
||||
|
||||
assert specs == [
|
||||
{
|
||||
"title": "Harden coding retro smoke gates",
|
||||
"description": "Dry-run with fixture and live hub evidence.",
|
||||
"target_repo": "activity-core",
|
||||
"priority": "high",
|
||||
"labels": ["coding-retro", "improvement", "automated"],
|
||||
"due_in_days": None,
|
||||
"source_type": "rule",
|
||||
"source_id": "propose-weekly-improvements",
|
||||
"triggering_event_id": "",
|
||||
"activity_definition_id": "",
|
||||
"condition": "context.s.score > 0",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_pipeline_emits_one_task_for_stale_repo_only():
|
||||
"""Stale repo (45 days) matches; fresh repo (10 days) does not."""
|
||||
defn = parse_file(_SBOM_DEF_PATH)
|
||||
|
||||
@@ -157,6 +157,128 @@ def test_repo_sbom_status_returns_empty_on_failure(monkeypatch) -> None:
|
||||
assert resolver.resolve("repo_sbom_status", None, {"repos": "all"}) == {}
|
||||
|
||||
|
||||
def test_coding_retro_returns_latest_progress_suggestions(monkeypatch) -> None:
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
|
||||
calls.append({"url": url, **kwargs})
|
||||
return DummyResponse([
|
||||
{
|
||||
"id": "older-retro",
|
||||
"event_type": "coding_retro",
|
||||
"summary": "older",
|
||||
"created_at": "2026-05-31T17:00:00Z",
|
||||
"detail": {
|
||||
"generated_at": "2026-05-31T17:00:00Z",
|
||||
"suggestions": [
|
||||
{
|
||||
"repo": "old-repo",
|
||||
"title": "Old recommendation",
|
||||
"recommendation": "Do the older thing.",
|
||||
"priority": "low",
|
||||
"score": 1,
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "note-1",
|
||||
"event_type": "note",
|
||||
"summary": "ignore me",
|
||||
"created_at": "2026-06-07T17:05:00Z",
|
||||
"detail": {},
|
||||
},
|
||||
{
|
||||
"id": "newer-retro",
|
||||
"event_type": "coding_retro",
|
||||
"summary": "weekly coding retro ready",
|
||||
"created_at": "2026-06-07T17:10:00Z",
|
||||
"detail": {
|
||||
"generated_at": "2026-06-07T17:09:30Z",
|
||||
"window": {
|
||||
"since": "2026-05-31T00:00:00Z",
|
||||
"until": "2026-06-07T00:00:00Z",
|
||||
},
|
||||
"suggestions": [
|
||||
{
|
||||
"target_repo": "activity-core",
|
||||
"title": "Harden schedule smoke gates",
|
||||
"description": "Add a smoke proof before enablement.",
|
||||
"priority": "HIGH",
|
||||
"score": "8.5",
|
||||
},
|
||||
{
|
||||
"repo_slug": "repo-without-title",
|
||||
"recommendation": "missing title should be skipped",
|
||||
"score": 9,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
monkeypatch.setenv("STATE_HUB_URL", "http://state-hub.test/")
|
||||
monkeypatch.setattr(httpx, "get", fake_get)
|
||||
|
||||
result = StateHubContextResolver().resolve(
|
||||
"coding_retro",
|
||||
None,
|
||||
{"limit": 20, "window_days": 7},
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
{
|
||||
"url": "http://state-hub.test/progress/",
|
||||
"params": {"limit": 20},
|
||||
"timeout": 10.0,
|
||||
}
|
||||
]
|
||||
assert result["source_progress_id"] == "newer-retro"
|
||||
assert result["generated_at"] == "2026-06-07T17:09:30Z"
|
||||
assert result["window"] == {
|
||||
"since": "2026-05-31T00:00:00Z",
|
||||
"until": "2026-06-07T00:00:00Z",
|
||||
}
|
||||
assert result["summary"] == "weekly coding retro ready"
|
||||
assert result["suggestions"] == [
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"title": "Harden schedule smoke gates",
|
||||
"recommendation": "Add a smoke proof before enablement.",
|
||||
"priority": "high",
|
||||
"score": 8.5,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_coding_retro_returns_empty_shape_when_not_published(monkeypatch) -> None:
|
||||
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
|
||||
return DummyResponse([
|
||||
{
|
||||
"id": "note-1",
|
||||
"event_type": "note",
|
||||
"created_at": "2026-06-07T17:10:00Z",
|
||||
}
|
||||
])
|
||||
|
||||
monkeypatch.setattr(httpx, "get", fake_get)
|
||||
|
||||
result = StateHubContextResolver().resolve(
|
||||
"coding_retro",
|
||||
None,
|
||||
{"event_type": "coding_retro"},
|
||||
)
|
||||
|
||||
assert result == {
|
||||
"suggestions": [],
|
||||
"window": None,
|
||||
"generated_at": None,
|
||||
"source_progress_id": None,
|
||||
"event_type": "coding_retro",
|
||||
"summary": "",
|
||||
}
|
||||
|
||||
|
||||
def test_resolver_failure_returns_empty(monkeypatch) -> None:
|
||||
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
|
||||
raise httpx.ConnectError("offline")
|
||||
|
||||
Reference in New Issue
Block a user