Skip to main content

M15: GitHub ↔ Beads Bridge

2026-02-22T08:31:23Z by Showboat 0.6.0

Act 1: Verify label-map.json includes triggerLabel field.

cat scripts/label-map.json
{
"labels": {
"bug": {"beadLabel": "dev", "priority": 1},
"enhancement": {"beadLabel": "eng-mgr", "priority": 2},
"documentation": {"beadLabel": "dev", "priority": 3},
"urgent": {"priority": 0}
},
"defaults": {
"beadLabel": "eng-mgr",
"priority": 2
},
"triggerLabel": "ludus"
}

Act 2: Verify close-bead.sh handles GH# references for bidirectional GitHub bridge.

grep -n 'GH#\|gh issue close\|auto-detect repo' agents/shared/skills/beads/close-bead.sh
6:# Extracts GH#<number> from bead title. If found, closes the GitHub issue
78:# Extract GH#<number> from title
82: # Extract the number after GH#
85:m = re.search(r'GH#(\d+)', sys.stdin.read())
92: echo "No GH# reference found in bead title, skipping GitHub close" >&2
97:echo "Found GitHub issue reference: GH#$GH_NUMBER" >&2
113: echo "Warning: Cannot determine GitHub repo for GH#$GH_NUMBER, skipping GitHub close" >&2
121:gh issue close "$GH_NUMBER" --repo "$REPO" \

Act 3: Run all M15 tests (existing gh-event-poll.py tests — must still pass).

uv run pytest tests/test_m15* -v
============================= test session starts ==============================
platform darwin -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0 -- /Users/mhild/src/durandom/openclaw/b4arena/.venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/mhild/src/durandom/openclaw/b4arena
configfile: pyproject.toml
collecting ... collected 39 items

tests/test_m15_gh_event_poll.py::TestFetchEvents::test_200_returns_events_and_etag PASSED [ 2%]
tests/test_m15_gh_event_poll.py::TestFetchEvents::test_304_returns_empty PASSED [ 5%]
tests/test_m15_gh_event_poll.py::TestFetchEvents::test_etag_sent_as_if_none_match PASSED [ 7%]
tests/test_m15_gh_event_poll.py::TestFilterEvents::test_filters_by_type_and_action PASSED [ 10%]
tests/test_m15_gh_event_poll.py::TestFilterEvents::test_filters_by_trigger_label PASSED [ 12%]
tests/test_m15_gh_event_poll.py::TestFilterEvents::test_skips_old_event_ids PASSED [ 15%]
tests/test_m15_gh_event_poll.py::TestFilterEvents::test_sorts_ascending PASSED [ 17%]
tests/test_m15_gh_event_poll.py::TestFilterEvents::test_numeric_id_comparison PASSED [ 20%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_parses_agent_and_priority PASSED [ 23%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_no_ludus_returns_none PASSED [ 25%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_empty_body PASSED [ 28%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_malformed_pairs_ignored PASSED [ 30%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_multiple_colons_in_value PASSED [ 33%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_strips_ludus_line PASSED [ 35%]
tests/test_m15_gh_event_poll.py::TestLudusCommand::test_strips_empty_body PASSED [ 38%]
tests/test_m15_gh_event_poll.py::TestLabelMapping::test_bug_maps_to_dev_p1 PASSED [ 41%]
tests/test_m15_gh_event_poll.py::TestLabelMapping::test_enhancement_maps_to_eng_mgr_p2 PASSED [ 43%]
tests/test_m15_gh_event_poll.py::TestLabelMapping::test_unknown_label_uses_defaults PASSED [ 46%]
tests/test_m15_gh_event_poll.py::TestLabelMapping::test_urgent_overrides_priority_only PASSED [ 48%]
tests/test_m15_gh_event_poll.py::TestLabelMapping::test_min_priority_wins PASSED [ 51%]
tests/test_m15_gh_event_poll.py::TestRoutingPrecedence::test_ludus_agent_overrides_label_map PASSED [ 53%]
tests/test_m15_gh_event_poll.py::TestRoutingPrecedence::test_ludus_priority_overrides_label_priority PASSED [ 56%]
tests/test_m15_gh_event_poll.py::TestRoutingPrecedence::test_no_ludus_falls_back_to_labels PASSED [ 58%]
tests/test_m15_gh_event_poll.py::TestRoutingPrecedence::test_no_labels_falls_back_to_defaults PASSED [ 61%]
tests/test_m15_gh_event_poll.py::TestPollRepo::test_events_create_bead PASSED [ 64%]
tests/test_m15_gh_event_poll.py::TestPollRepo::test_304_creates_no_bead PASSED [ 66%]
tests/test_m15_gh_event_poll.py::TestPollRepo::test_idempotency_skips_existing PASSED [ 69%]
tests/test_m15_gh_event_poll.py::TestStateFiles::test_etag_persisted PASSED [ 71%]
tests/test_m15_gh_event_poll.py::TestStateFiles::test_last_event_id_persisted PASSED [ 74%]
tests/test_m15_gh_event_poll.py::TestStateFiles::test_separate_state_per_repo PASSED [ 76%]
tests/test_m15_gh_event_poll.py::TestStateFiles::test_fresh_run_without_state PASSED [ 79%]
tests/test_m15_gh_event_poll.py::TestStateFiles::test_state_updated_after_poll PASSED [ 82%]
tests/test_m15_gh_event_poll.py::TestFileLocking::test_locked_file_exits_zero PASSED [ 84%]
tests/test_m15_gh_event_poll.py::TestFileLocking::test_lock_released_after_completion PASSED [ 87%]
tests/test_m15_gh_event_poll.py::TestDryRun::test_dry_run_creates_no_beads PASSED [ 89%]
tests/test_m15_gh_event_poll.py::TestMultiRepo::test_two_repos_separate_state PASSED [ 92%]
tests/test_m15_gh_event_poll.py::TestBuildDescription::test_includes_repo_url_and_body PASSED [ 94%]
tests/test_m15_gh_event_poll.py::TestBuildDescription::test_strips_ludus_from_description PASSED [ 97%]
tests/test_m15_gh_event_poll.py::TestBuildDescription::test_empty_body PASSED [100%]

============================== 39 passed in 9.50s ==============================