agentirc-cli 9.2.0__tar.gz → 9.3.0__tar.gz
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.
- agentirc_cli-9.3.0/.claude/skills/pr-review/scripts/pr-sonar.sh +93 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/workflow.sh +8 -1
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/CHANGELOG.md +86 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/CLAUDE.md +16 -7
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/PKG-INFO +3 -3
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/server_link.py +1 -1
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/docs/superpowers/specs/2026-04-30-bootstrap-design.md +11 -2
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/pyproject.toml +182 -4
- agentirc_cli-9.3.0/tests/_helpers.py +96 -0
- agentirc_cli-9.3.0/tests/conftest.py +295 -0
- agentirc_cli-9.3.0/tests/telemetry/__init__.py +0 -0
- agentirc_cli-9.3.0/tests/telemetry/_fakes.py +26 -0
- agentirc_cli-9.3.0/tests/telemetry/_metrics_helpers.py +75 -0
- agentirc_cli-9.3.0/tests/telemetry/test_audit_emit.py +145 -0
- agentirc_cli-9.3.0/tests/telemetry/test_audit_lifecycle.py +69 -0
- agentirc_cli-9.3.0/tests/telemetry/test_audit_module.py +306 -0
- agentirc_cli-9.3.0/tests/telemetry/test_audit_parse_error.py +177 -0
- agentirc_cli-9.3.0/tests/telemetry/test_config.py +26 -0
- agentirc_cli-9.3.0/tests/telemetry/test_dispatch_span.py +45 -0
- agentirc_cli-9.3.0/tests/telemetry/test_emit_event_span.py +42 -0
- agentirc_cli-9.3.0/tests/telemetry/test_metrics_init.py +58 -0
- agentirc_cli-9.3.0/tests/telemetry/test_metrics_s2s.py +160 -0
- agentirc_cli-9.3.0/tests/telemetry/test_outbound_inject.py +70 -0
- agentirc_cli-9.3.0/tests/telemetry/test_parse_error.py +33 -0
- agentirc_cli-9.3.0/tests/telemetry/test_s2s_relay_span.py +114 -0
- agentirc_cli-9.3.0/tests/telemetry/test_server_init.py +29 -0
- agentirc_cli-9.3.0/tests/telemetry/test_server_link_inject.py +124 -0
- agentirc_cli-9.3.0/tests/telemetry/test_tracing.py +49 -0
- agentirc_cli-9.3.0/tests/test_channel.py +131 -0
- agentirc_cli-9.3.0/tests/test_connection.py +135 -0
- agentirc_cli-9.3.0/tests/test_discovery.py +169 -0
- agentirc_cli-9.3.0/tests/test_events_basic.py +284 -0
- agentirc_cli-9.3.0/tests/test_events_catalog.py +70 -0
- agentirc_cli-9.3.0/tests/test_events_federation.py +110 -0
- agentirc_cli-9.3.0/tests/test_events_history.py +64 -0
- agentirc_cli-9.3.0/tests/test_events_lifecycle.py +343 -0
- agentirc_cli-9.3.0/tests/test_events_reserved_nick.py +68 -0
- agentirc_cli-9.3.0/tests/test_federation.py +1163 -0
- agentirc_cli-9.3.0/tests/test_history.py +553 -0
- agentirc_cli-9.3.0/tests/test_link_reconnect.py +141 -0
- agentirc_cli-9.3.0/tests/test_mentions.py +155 -0
- agentirc_cli-9.3.0/tests/test_messaging.py +103 -0
- agentirc_cli-9.3.0/tests/test_modes.py +327 -0
- agentirc_cli-9.3.0/tests/test_room_persistence.py +75 -0
- agentirc_cli-9.3.0/tests/test_rooms_federation.py +63 -0
- agentirc_cli-9.3.0/tests/test_rooms_integration.py +110 -0
- agentirc_cli-9.3.0/tests/test_server_icon_skill.py +214 -0
- agentirc_cli-9.3.0/tests/test_skills.py +212 -0
- agentirc_cli-9.3.0/tests/test_threads.py +384 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/uv.lock +3 -118
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/portability-lint.sh +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/pr-batch.sh +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/pr-comments.sh +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/pr-reply.sh +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills/pr-review/scripts/pr-status.sh +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.claude/skills.local.yaml.example +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/.gitignore +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/LICENSE +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/README.md +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/__main__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/aio.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/bots/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/bots/bot_manager.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/bots/http_listener.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/cli_shared/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/cli_shared/constants.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/cli_shared/mesh.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/constants.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/pidfile.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/protocol/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/protocol/message.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/protocol/replies.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/telemetry/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/telemetry/audit.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/telemetry/context.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/telemetry/metrics.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/telemetry/tracing.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/_internal/virtual_client.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/channel.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/cli.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/client.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/config.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/events.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/history_store.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/ircd.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/protocol.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/remote_client.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/room_store.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/rooms_util.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skill.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skills/history.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skills/icon.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/skills/threads.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/agentirc/thread_store.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/docs/steward/onboarding.md +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/tests/__init__.py +0 -0
- {agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-sonar.sh — list every SonarCloud finding attached to a PR.
|
|
3
|
+
#
|
|
4
|
+
# Surfaces what the GitHub-side `poll` cannot: SonarCloud issues,
|
|
5
|
+
# security hotspots, and the duplication breakdown. The
|
|
6
|
+
# sonarqubecloud[bot] PR comment only links to the dashboard — actual
|
|
7
|
+
# findings live behind the SonarCloud API.
|
|
8
|
+
#
|
|
9
|
+
# Usage: pr-sonar.sh [--repo OWNER/REPO] [--sonar-key KEY] PR_NUMBER
|
|
10
|
+
#
|
|
11
|
+
# Defaults:
|
|
12
|
+
# --repo auto-detected via `gh repo view`
|
|
13
|
+
# --sonar-key derived from repo as `<owner>_<name>`
|
|
14
|
+
#
|
|
15
|
+
# Anonymous SonarCloud access is sufficient for public projects.
|
|
16
|
+
# Requires: gh, jq, curl, python3.
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
REPO=""
|
|
21
|
+
SONAR_KEY=""
|
|
22
|
+
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--repo) REPO="$2"; shift 2 ;;
|
|
26
|
+
--sonar-key) SONAR_KEY="$2"; shift 2 ;;
|
|
27
|
+
*) break ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
PR_NUMBER="${1:?Usage: pr-sonar.sh [--repo OWNER/REPO] [--sonar-key KEY] PR_NUMBER}"
|
|
32
|
+
|
|
33
|
+
if [[ -z "$REPO" ]]; then
|
|
34
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
35
|
+
fi
|
|
36
|
+
if [[ -z "$SONAR_KEY" ]]; then
|
|
37
|
+
SONAR_KEY="${REPO%%/*}_${REPO##*/}"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "════════════════ SONARCLOUD (project=$SONAR_KEY, PR=$PR_NUMBER) ════════════════"
|
|
41
|
+
|
|
42
|
+
# ── Quality gate
|
|
43
|
+
QG=$(curl -fsS "https://sonarcloud.io/api/qualitygates/project_status?projectKey=${SONAR_KEY}&pullRequest=${PR_NUMBER}" || echo '{}')
|
|
44
|
+
QG_STATUS=$(echo "$QG" | jq -r '.projectStatus.status // "UNKNOWN"')
|
|
45
|
+
echo "Quality Gate: $QG_STATUS"
|
|
46
|
+
echo "$QG" | jq -r '.projectStatus.conditions[]? | select(.status != "OK") | " ✗ \(.metricKey) = \(.actualValue) (threshold \(.comparator) \(.errorThreshold))"' || true
|
|
47
|
+
|
|
48
|
+
# ── Issues (BUG / VULNERABILITY / CODE_SMELL)
|
|
49
|
+
ISSUES=$(curl -fsS "https://sonarcloud.io/api/issues/search?componentKeys=${SONAR_KEY}&pullRequest=${PR_NUMBER}&statuses=OPEN,CONFIRMED&ps=500" || echo '{}')
|
|
50
|
+
ISSUE_TOTAL=$(echo "$ISSUES" | jq -r '.total // 0')
|
|
51
|
+
echo
|
|
52
|
+
echo "── Issues ($ISSUE_TOTAL OPEN/CONFIRMED) ─────────────────────────────"
|
|
53
|
+
if [[ "$ISSUE_TOTAL" != "0" ]]; then
|
|
54
|
+
echo "$ISSUES" | jq -r '
|
|
55
|
+
.issues[] |
|
|
56
|
+
" [\(.rule)] \(.severity) \(.component | sub("^[^:]+:"; ""))(:\(.line // "?"))\n \(.message)"
|
|
57
|
+
'
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# ── Security hotspots
|
|
61
|
+
HOTSPOTS=$(curl -fsS "https://sonarcloud.io/api/hotspots/search?projectKey=${SONAR_KEY}&pullRequest=${PR_NUMBER}&status=TO_REVIEW&ps=500" || echo '{}')
|
|
62
|
+
HOTSPOT_TOTAL=$(echo "$HOTSPOTS" | jq -r '.paging.total // 0')
|
|
63
|
+
echo
|
|
64
|
+
echo "── Hotspots ($HOTSPOT_TOTAL TO_REVIEW) ──────────────────────────────"
|
|
65
|
+
if [[ "$HOTSPOT_TOTAL" != "0" ]]; then
|
|
66
|
+
echo "$HOTSPOTS" | jq -r '
|
|
67
|
+
.hotspots[] |
|
|
68
|
+
" [\(.ruleKey)] \(.vulnerabilityProbability) \(.component | sub("^[^:]+:"; ""))(:\(.line // "?"))\n \(.message)"
|
|
69
|
+
'
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# ── Duplication
|
|
73
|
+
MEAS=$(curl -fsS "https://sonarcloud.io/api/measures/component?component=${SONAR_KEY}&pullRequest=${PR_NUMBER}&metricKeys=new_duplicated_lines_density,new_duplicated_lines,new_duplicated_blocks" || echo '{}')
|
|
74
|
+
DUP_PCT=$(echo "$MEAS" | jq -r '.component.measures[]? | select(.metric == "new_duplicated_lines_density") | .periods[0].value // ""')
|
|
75
|
+
DUP_LINES=$(echo "$MEAS" | jq -r '.component.measures[]? | select(.metric == "new_duplicated_lines") | .periods[0].value // ""')
|
|
76
|
+
DUP_BLOCKS=$(echo "$MEAS" | jq -r '.component.measures[]? | select(.metric == "new_duplicated_blocks") | .periods[0].value // ""')
|
|
77
|
+
echo
|
|
78
|
+
echo "── Duplication on new code ──────────────────────────────────────────"
|
|
79
|
+
echo " density: ${DUP_PCT:-?}% lines: ${DUP_LINES:-?} blocks: ${DUP_BLOCKS:-?}"
|
|
80
|
+
|
|
81
|
+
if [[ "${DUP_BLOCKS:-0}" != "0" && -n "${DUP_BLOCKS:-}" ]]; then
|
|
82
|
+
# Per-file duplication: list every file with duplicated_lines on new code.
|
|
83
|
+
# Sonar doesn't expose per-PR duplication-by-file directly; surface the
|
|
84
|
+
# files that appear in the issues + hotspots as a weak proxy.
|
|
85
|
+
DUP_FILES=$(echo "$ISSUES" | jq -r '[.issues[] | .component | sub("^[^:]+:"; "")] | unique | .[]')
|
|
86
|
+
if [[ -n "$DUP_FILES" ]]; then
|
|
87
|
+
echo " files with findings (likely duplication source):"
|
|
88
|
+
echo "$DUP_FILES" | sed 's/^/ - /'
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo
|
|
93
|
+
echo "Dashboard: https://sonarcloud.io/dashboard?id=${SONAR_KEY}&pullRequest=${PR_NUMBER}"
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Subcommands:
|
|
5
5
|
# lint run the portability lint on the current diff (staged + unstaged)
|
|
6
|
-
# poll <PR> fetch and display review comments
|
|
6
|
+
# poll <PR> fetch and display review comments + SonarCloud findings
|
|
7
|
+
# sonar <PR> list every SonarCloud issue / hotspot / duplication on a PR
|
|
7
8
|
# reply <PR> batch reply to review comments (JSONL on stdin), --resolve
|
|
8
9
|
# delta dump CLAUDE.md head + culture.yaml for each sibling project
|
|
9
10
|
# listed in skills.local.yaml (alignment-delta check)
|
|
@@ -53,6 +54,12 @@ case "$cmd" in
|
|
|
53
54
|
poll)
|
|
54
55
|
PR="${1:?Usage: workflow.sh poll <PR>}"
|
|
55
56
|
bash "$SCRIPT_DIR/pr-comments.sh" "$PR"
|
|
57
|
+
echo
|
|
58
|
+
bash "$SCRIPT_DIR/pr-sonar.sh" "$PR"
|
|
59
|
+
;;
|
|
60
|
+
sonar)
|
|
61
|
+
PR="${1:?Usage: workflow.sh sonar <PR>}"
|
|
62
|
+
bash "$SCRIPT_DIR/pr-sonar.sh" "$PR"
|
|
56
63
|
;;
|
|
57
64
|
reply)
|
|
58
65
|
PR="${1:?Usage: workflow.sh reply <PR> (JSONL on stdin)}"
|
|
@@ -4,6 +4,92 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [9.3.0] - 2026-05-01
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Test suite migration (PR-B3): 36 server-core / telemetry tests
|
|
12
|
+
vendored from `culture@df50942` (~6,500 LOC). 315 tests run under
|
|
13
|
+
`pytest -n auto` in ~29 seconds.
|
|
14
|
+
- 21 server-core tests in `tests/` cover IRC lifecycle, channels,
|
|
15
|
+
rooms, threads, history, federation (S2S), events, mentions,
|
|
16
|
+
and the icon skill.
|
|
17
|
+
- 15 telemetry integration tests in `tests/telemetry/` cover audit
|
|
18
|
+
JSONL emission, OTLP span injection on dispatch, S2S relay
|
|
19
|
+
spans, metrics initialization, and trace-context propagation.
|
|
20
|
+
- Test helper modules `_fakes.py` and `_metrics_helpers.py` also
|
|
21
|
+
vendored verbatim under the same package.
|
|
22
|
+
- `tests/conftest.py` — adapted from culture. Drops the
|
|
23
|
+
`_BOTS_DIR_*` patches and the `server_with_bot` /
|
|
24
|
+
`server_with_bots` fixtures (see Changed below). Keeps the
|
|
25
|
+
`IRCTestClient` raw-TCP helper and the IRCd lifecycle, telemetry,
|
|
26
|
+
and audit fixtures.
|
|
27
|
+
- `.claude/skills/pr-review/scripts/pr-sonar.sh` — new script that
|
|
28
|
+
fetches every SonarCloud issue, security hotspot, and duplication
|
|
29
|
+
measure for a PR via the SonarCloud API. `workflow.sh poll` now
|
|
30
|
+
runs it after `pr-comments.sh`; `workflow.sh sonar <PR>` runs it
|
|
31
|
+
standalone. Closes the gap where the GitHub-side poll only saw
|
|
32
|
+
the SonarCloud bot's "Quality Gate failed" link without the
|
|
33
|
+
underlying findings. Re-vendor to `steward` after this PR merges.
|
|
34
|
+
- Three `[tool.citation]` packages:
|
|
35
|
+
`culture-tests-conftest` (paraphrase),
|
|
36
|
+
`culture-tests-server-core` (mostly quote, paraphrase for
|
|
37
|
+
`test_events_basic.py`), and `culture-tests-telemetry` (mostly
|
|
38
|
+
quote, paraphrase for `test_tracing.py`).
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `agentirc/server_link.py:_replay_event` — parameter renamed from
|
|
43
|
+
`_seq` (PR-B1's unused-arg compliance variant) back to `seq` to
|
|
44
|
+
match the upstream signature culture's tests assume; signature
|
|
45
|
+
carries `# noqa: ARG002 # NOSONAR S1172`. Hash refreshed.
|
|
46
|
+
|
|
47
|
+
### Fixed (post-review)
|
|
48
|
+
|
|
49
|
+
- `requires-python = ">=3.10"` → `">=3.11"`; classifiers updated
|
|
50
|
+
(drop 3.10, add 3.13). Resolves Copilot/Qodo "asyncio.timeout
|
|
51
|
+
not in 3.10" findings. The 3.10 floor was inherited from PR-B1/B2
|
|
52
|
+
and would have ImportError'd on the first test run.
|
|
53
|
+
- 2 SonarCloud `python:S5332` security hotspots cleared via
|
|
54
|
+
`# NOSONAR S5332` annotations on `tests/telemetry/test_config.py`'s
|
|
55
|
+
localhost OTLP test fixtures (URLs never reach the wire).
|
|
56
|
+
- New `tests/_helpers.py` (agentirc-native, not cited) extracts the
|
|
57
|
+
duplicated "boot two linked IRCds" pattern into `boot_linked_pair`
|
|
58
|
+
+ `link_pair`. Refactored 9 sites that previously inlined ~22
|
|
59
|
+
lines of identical scaffolding: 5 in `test_federation.py`, 3 in
|
|
60
|
+
`test_link_reconnect.py`, plus the `linked_servers` conftest
|
|
61
|
+
fixture. Drops ~180 duplicated lines, addressing SonarCloud's
|
|
62
|
+
>3% duplication threshold while keeping the cited test bodies
|
|
63
|
+
readable. Re-snapshots from culture replay this extraction
|
|
64
|
+
mechanically.
|
|
65
|
+
- 42 SonarCloud OPEN issues cleared via inline `# NOSONAR <rule>`
|
|
66
|
+
annotations: `python:S1172` (server_link `_replay_event`'s upstream
|
|
67
|
+
signature compat), `python:S2068` (3 test fixture passwords),
|
|
68
|
+
`python:S7483` (3 `IRCTestClient.recv*` / `_wait_for_span` timeout
|
|
69
|
+
params kept by upstream design — see `RECV_TIMEOUT_SECONDS`
|
|
70
|
+
module-level note), `python:S7494` (2 `dict(t.split("=") for t in
|
|
71
|
+
tags)` patterns vendored verbatim from culture), `python:S125`
|
|
72
|
+
(1 `# 311 RPL_WHOISUSER` documentation comment SonarCloud
|
|
73
|
+
misclassified as commented-out code), `python:S1481` × 21
|
|
74
|
+
(`server_a, server_b = linked_servers` tuple-unpack sites where
|
|
75
|
+
one or both vars are used by the federation fixture but unread in
|
|
76
|
+
the test body — same idiom across 3 files; underscore-prefix
|
|
77
|
+
rename would diverge from culture upstream and complicate
|
|
78
|
+
re-snapshots).
|
|
79
|
+
|
|
80
|
+
### Deferred / out of scope
|
|
81
|
+
|
|
82
|
+
- Three bot-fixtured telemetry tests
|
|
83
|
+
(`test_bot_event_dispatch_span.py`, `test_bot_run_span.py`,
|
|
84
|
+
`test_metrics_bots.py`) and `test_welcome_bot.py` stay in culture
|
|
85
|
+
— they depend on the real `BotManager` (forbidden by agentirc's
|
|
86
|
+
dependency boundary). Bucket C tests (cli/console/daemon/clients)
|
|
87
|
+
also remain in culture indefinitely.
|
|
88
|
+
- `tests/telemetry/conftest.py` is not migrated; its only consumer
|
|
89
|
+
was the deferred bot-fixtured tests above.
|
|
90
|
+
- Bootstrap docs (`docs/api-stability.md`, `docs/cli.md`,
|
|
91
|
+
`docs/deployment.md`) ship in PR-B4.
|
|
92
|
+
|
|
7
93
|
## [9.2.0] - 2026-05-01
|
|
8
94
|
|
|
9
95
|
### Added
|
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
4
|
|
|
5
|
-
## Current state:
|
|
5
|
+
## Current state: bootstrap functionally complete (9.3.0); docs slice (PR-B4) remains
|
|
6
6
|
|
|
7
|
-
This repo is the agentirc server-core extraction out of the sibling project [`culture`](https://github.com/OriNachum/culture). As of 9.
|
|
7
|
+
This repo is the agentirc server-core extraction out of the sibling project [`culture`](https://github.com/OriNachum/culture). As of 9.3.0:
|
|
8
8
|
|
|
9
9
|
- **Server-core** (`agentirc/{ircd,server_link,channel,events,skill,remote_client,…}.py`, `agentirc/skills/{rooms,threads,history,icon}.py`) — vendored from `culture@df50942` via the `cite-don't-copy` pattern (see `[tool.citation]` in `pyproject.toml`).
|
|
10
10
|
- **Client transport** (`agentirc/client.py`) — vendored from `culture/agentirc/client.py` in PR-B2. The bootstrap spec originally said this would "stay in culture", but the dependency-boundary analysis after PR-B1 showed `client.py` only imports already-vendored support modules plus opentelemetry. Without it, `agentirc/ircd.py:580`'s runtime `from agentirc.client import Client` raised `ImportError` on the first TCP IRC connection.
|
|
11
11
|
- **Public CLI** (`agentirc/cli.py`) — real verb dispatch extracted from `culture/cli/server.py`. Verbs: `serve` (foreground, no PID; for systemd `Type=simple` and containers), `start`/`stop`/`status` (lifecycle), `restart`, `link` (peer-spec validator), `logs` (cat / tail of `~/.culture/logs/server-<name>.log`), `version`.
|
|
12
12
|
- **Public protocol** (`agentirc/protocol.py`) — verb name constants, numerics, IRCv3 tag names. Wire-format quirks (`ROOMETAEND`, `ROOMETASET` typos, `ERR_NOSUCHCHANNEL` semantic misuse, `STHREAD` verb collapse) preserved verbatim — they need coordinated cross-repo bumps to fix.
|
|
13
|
+
- **Test suite** (PR-B3, 9.3.0) — 36 tests vendored from `culture@df50942` (~6.5kloc), 315 tests run under `pytest -n auto` in ~29s on default workers. Three telemetry tests (`test_bot_event_dispatch_span`, `test_bot_run_span`, `test_metrics_bots`) and `test_welcome_bot` stay in culture because they depend on the real `BotManager`. `tests/conftest.py` was adapted to drop bot-loader sandboxing and the `server_with_bot` / `server_with_bots` fixtures.
|
|
13
14
|
- **Internal support** (`agentirc/_internal/`) — `aio`, `constants`, `protocol/`, `telemetry/`, `virtual_client`, `pidfile` (PR-B2), `cli_shared/` (PR-B2), `bots/` stubs.
|
|
14
15
|
|
|
15
16
|
End-to-end verified: `agentirc start --port <p>` boots a real IRCd, TCP NICK/USER handshake returns `001 RPL_WELCOME`, `agentirc stop` shuts cleanly. `agentirc serve` is byte-indistinguishable from `culture server start` for the lifecycle contract culture's shim relies on.
|
|
16
17
|
|
|
17
18
|
What is **not** done yet:
|
|
18
|
-
- **
|
|
19
|
+
- **Bootstrap docs (PR-B4)** — `docs/api-stability.md`, `docs/cli.md`, `docs/deployment.md`. Pure prose; not gating on culture's cutover (the public API surface is already importable; PR-B4 just documents the contract).
|
|
19
20
|
|
|
20
21
|
Read the bootstrap spec at `docs/superpowers/specs/2026-04-30-bootstrap-design.md` for the full plan; it is the operative source of truth and is intentionally self-contained. The culture-side counterpart spec is at `../culture/docs/superpowers/specs/2026-04-30-agentirc-extraction-design.md` — not normally needed, but explains *why* if a decision looks arbitrary.
|
|
21
22
|
|
|
@@ -44,6 +45,13 @@ There are three different names in play. Don't conflate them:
|
|
|
44
45
|
|
|
45
46
|
When migrating tests, the rule is: pure server tests come here, transport tests **also** come here now that we own `client.py`, mixed tests stay in culture and get rewritten to drive `agentirc serve` as a subprocess fixture rather than importing `IRCd` directly. When unsure, **prefer copying the test here** — this repo owns the IRCd and the client transport.
|
|
46
47
|
|
|
48
|
+
### Test layout (since 9.3.0)
|
|
49
|
+
|
|
50
|
+
- `tests/conftest.py` — paraphrase of culture's conftest. Drops the `_BOTS_DIR_*` `unittest.mock.patch` calls (no-op against agentirc's bot stubs) and the `server_with_bot` / `server_with_bots` fixtures. Keeps `IRCTestClient`, the IRCd lifecycle fixtures (`server`, `linked_servers`, `make_client*`, `server_welcome_disabled`), telemetry fixtures (`tracing_exporter`, `metrics_reader`, `audit_dir`), and the `TEST_LINK_PASSWORD` constant.
|
|
51
|
+
- `tests/test_*.py` — 21 server-core tests + the agentirc-native `test_cli.py`. Cover IRC lifecycle, channels, rooms, threads, history, federation, events, mentions, the icon skill.
|
|
52
|
+
- `tests/telemetry/test_*.py` — 15 telemetry integration tests covering audit JSONL emission, OTLP span injection on dispatch, S2S relay spans, metrics initialization, trace-context propagation. Uses two private helper modules `_fakes.py` (FakeWriter etc.) and `_metrics_helpers.py`. No `tests/telemetry/conftest.py` (the upstream one was bot-coupled).
|
|
53
|
+
- Tests left in culture: `test_bot_event_dispatch_span.py`, `test_bot_run_span.py`, `test_metrics_bots.py`, `test_welcome_bot.py` (bot-manager-coupled), plus the entire bucket-C surface (cli, console, daemon, clients, credentials).
|
|
54
|
+
|
|
47
55
|
## Public API contract (semver-tracked)
|
|
48
56
|
|
|
49
57
|
Only three modules are public. Everything else is internal and may be refactored without a major bump.
|
|
@@ -82,7 +90,7 @@ Do not rename on-disk artifacts during the bootstrap. That is explicitly out of
|
|
|
82
90
|
# Dev setup
|
|
83
91
|
uv venv && uv pip install -e ".[dev]"
|
|
84
92
|
|
|
85
|
-
# Tests (
|
|
93
|
+
# Tests (315 collected, ~29s on default workers)
|
|
86
94
|
pytest -n auto
|
|
87
95
|
|
|
88
96
|
# Run a single test
|
|
@@ -91,7 +99,7 @@ pytest tests/path/to/test_file.py::test_name -v
|
|
|
91
99
|
# CLI smoke
|
|
92
100
|
agentirc --help
|
|
93
101
|
agentirc-cli --help # alias of agentirc
|
|
94
|
-
agentirc version # prints "agentirc 9.
|
|
102
|
+
agentirc version # prints "agentirc 9.3.0"
|
|
95
103
|
python -m agentirc version # equivalent
|
|
96
104
|
|
|
97
105
|
# Lifecycle (functional since 9.2.0)
|
|
@@ -146,7 +154,8 @@ Per-machine paths for these skills go in `.claude/skills.local.yaml` (gitignored
|
|
|
146
154
|
|
|
147
155
|
The full list lives in §"Acceptance criteria" of the bootstrap spec. The non-obvious ones:
|
|
148
156
|
|
|
149
|
-
- `pip install agentirc-cli==9.
|
|
157
|
+
- `pip install agentirc-cli==9.3.0` on a clean venv produces working `agentirc` *and* `agentirc-cli` binaries (both pointing at `agentirc.cli:main`). ✅ since 9.0.0.
|
|
150
158
|
- `agentirc serve` is byte-indistinguishable from `culture server start` (same socket, same logs, same systemd integration). ✅ since 9.2.0.
|
|
151
159
|
- `agentirc.config.{ServerConfig, LinkConfig, TelemetryConfig}`, `agentirc.cli.{main, dispatch}`, `agentirc.protocol.*` all import from a clean Python session. ✅ since 9.2.0.
|
|
152
|
-
- `
|
|
160
|
+
- `pytest -n auto` passes for the migrated suite (315 tests, ~29s). ✅ since 9.3.0.
|
|
161
|
+
- `docs/api-stability.md` names the three public modules. ⏳ pending PR-B4.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentirc-cli
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.3.0
|
|
4
4
|
Summary: Agent-friendly IRCd: server core for AI agent meshes
|
|
5
5
|
Project-URL: Homepage, https://github.com/OriNachum/agentirc
|
|
6
6
|
Project-URL: Issues, https://github.com/OriNachum/agentirc/issues
|
|
@@ -32,11 +32,11 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
32
32
|
Classifier: Intended Audience :: Developers
|
|
33
33
|
Classifier: License :: OSI Approved :: MIT License
|
|
34
34
|
Classifier: Programming Language :: Python :: 3
|
|
35
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
36
35
|
Classifier: Programming Language :: Python :: 3.11
|
|
37
36
|
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
38
|
Classifier: Topic :: Communications :: Chat :: Internet Relay Chat
|
|
39
|
-
Requires-Python: >=3.
|
|
39
|
+
Requires-Python: >=3.11
|
|
40
40
|
Requires-Dist: opentelemetry-api>=1.22
|
|
41
41
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.22
|
|
42
42
|
Requires-Dist: opentelemetry-sdk>=1.22
|
|
@@ -968,7 +968,7 @@ class ServerLink:
|
|
|
968
968
|
except ValueError:
|
|
969
969
|
pass
|
|
970
970
|
|
|
971
|
-
async def _replay_event(self,
|
|
971
|
+
async def _replay_event(self, seq: int, event: Event) -> None: # noqa: ARG002 # NOSONAR S1172 — kept for upstream test signature compat
|
|
972
972
|
"""Replay a single event to the peer as S2S wire format."""
|
|
973
973
|
origin = self.server.config.name
|
|
974
974
|
# Federated events arrive with event.type as either an EventType enum
|
{agentirc_cli-9.2.0 → agentirc_cli-9.3.0}/docs/superpowers/specs/2026-04-30-bootstrap-design.md
RENAMED
|
@@ -172,8 +172,8 @@ Wire-format quirks (`ROOMETAEND`, `ROOMETASET` typos; `ERR_NOSUCHCHANNEL` semant
|
|
|
172
172
|
> - **Shape A — package skeleton** ✅ (PR #2, `9.0.0`): Tasks 1, 6, and a stub form of 9.
|
|
173
173
|
> - **Shape B-1 — server-core extraction** ✅ (PR #3, `9.1.0`): Tasks 2, 4, 9 (runtime deps), partial 10. See the "Cite-don't-copy" subsection below.
|
|
174
174
|
> - **Shape B-2 — real CLI + `protocol.py` + `client.py`** ✅ (PR-B2, `9.2.0`): Tasks 5, 7, plus vendoring `culture.pidfile`, `culture.cli.shared` (subset), and `culture/agentirc/client.py`. The bootstrap spec previously said `client.py` "stays in culture"; that decision was reversed in PR-B2 because (a) `agentirc/ircd.py:580`'s runtime `from agentirc.client import Client` was a guaranteed `ImportError` without it, and (b) `client.py` only imports already-vendored support modules — no backend-SDK pull-through.
|
|
175
|
-
> - **Shape B-3 — test suite migration** (
|
|
176
|
-
> - **Remaining**:
|
|
175
|
+
> - **Shape B-3 — test suite migration** ✅ (PR-B3, `9.3.0`): Task 8 + Task 13. 36 tests vendored from `culture@df50942` (~6.5kloc), 315 tests run under `pytest -n auto` in ~29s. `tests/conftest.py` adapted (paraphrase) to drop bot-loader sandboxing and bot-fixture definitions. Three telemetry/test_bot_*.py files plus `test_welcome_bot.py` stay in culture (BotManager-coupled). Bucket-C tests stay in culture indefinitely.
|
|
176
|
+
> - **Remaining**: 11–12 (docs — `api-stability.md`, `cli.md`, `deployment.md`, ship in PR-B4), 14 (acceptance criteria spot-check), 16–18 (tag, publish, report-back to culture).
|
|
177
177
|
>
|
|
178
178
|
> Task 3 (`Copy protocol/extensions/ wholesale`) is dropped: that path doesn't exist in culture. Re-add only if/when culture creates it.
|
|
179
179
|
|
|
@@ -219,6 +219,15 @@ Tests from culture are sorted into three buckets:
|
|
|
219
219
|
|
|
220
220
|
When in doubt, prefer moving tests *here* over leaving them in culture: this repo owns the IRCd, the client transport, and IRCd-internal tests should run in this repo's CI.
|
|
221
221
|
|
|
222
|
+
**Realised in PR-B3 (9.3.0):**
|
|
223
|
+
|
|
224
|
+
- 21 root server-core tests + 15 telemetry tests = 36 files (~6.5kloc) vendored verbatim with mechanical import rewrites (`culture.agentirc.X` → `agentirc.X`, `culture.protocol.message` → `agentirc._internal.protocol.message`, `culture.telemetry.X` → `agentirc._internal.telemetry.X`, `culture.agentirc.client` → `agentirc.client`).
|
|
225
|
+
- `tests/conftest.py` adapted as paraphrase: dropped the `_BOTS_DIR_*` `unittest.mock.patch` calls (no-op against agentirc's no-op `_internal/bots/` stubs) and the `server_with_bot` / `server_with_bots` fixtures (`culture.bots.*` is forbidden).
|
|
226
|
+
- `tests/telemetry/test_tracing.py` paraphrase: one `unittest.mock.patch` target rewritten from `"culture.telemetry.tracing.OTLPSpanExporter"` to `"agentirc._internal.telemetry.tracing.OTLPSpanExporter"`. OTEL service-name strings (`"culture.agentirc"`) and OTEL attribute keys (`"culture.s2s.*"`, `"culture.federation.peer"`, `"culture.dev/traceparent"`) preserved verbatim — they are public observability identifiers downstream consumers grep for.
|
|
227
|
+
- `tests/test_events_basic.py` paraphrase: `with patch("culture.bots.bot_manager.BOTS_DIR", …)` block removed (same dead-weight rationale as the conftest patches).
|
|
228
|
+
- `agentirc/server_link.py:_replay_event` parameter renamed from `_seq` (PR-B1's unused-arg compliance variant) back to `seq` to match the upstream signature culture's tests assume; signature carries a `# noqa: ARG002` to keep the linter quiet.
|
|
229
|
+
- **Stayed in culture:** `test_bot_event_dispatch_span.py`, `test_bot_run_span.py`, `test_metrics_bots.py` (need real `BotManager` for the bot-event path), `test_welcome_bot.py` (inspects `bot_manager.bots`), and the 57-file bucket-C surface (cli, console, daemon, clients, credentials, mesh_config). `tests/telemetry/conftest.py` not migrated — its only consumer was the deferred bot tests above.
|
|
230
|
+
|
|
222
231
|
## Acceptance criteria
|
|
223
232
|
|
|
224
233
|
- `pip install agentirc-cli==9.0.0` from PyPI produces working `agentirc` and `agentirc-cli` binaries on a clean venv (both pointing at `agentirc.cli:main`).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentirc-cli"
|
|
3
|
-
version = "9.
|
|
3
|
+
version = "9.3.0"
|
|
4
4
|
description = "Agent-friendly IRCd: server core for AI agent meshes"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Ori Nachum" },
|
|
9
9
|
]
|
|
@@ -19,9 +19,9 @@ classifiers = [
|
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"License :: OSI Approved :: MIT License",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.10",
|
|
23
22
|
"Programming Language :: Python :: 3.11",
|
|
24
23
|
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
25
|
"Topic :: Communications :: Chat :: Internet Relay Chat",
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
@@ -245,7 +245,7 @@ sha256 = "477760b85e4acf861a92aaf0ea73550c07267e3c33740a412a3075598bfb68aa"
|
|
|
245
245
|
|
|
246
246
|
[tool.citation.packages.culture-agentirc-server-core.files."server_link.py"]
|
|
247
247
|
status = "paraphrase"
|
|
248
|
-
sha256 = "
|
|
248
|
+
sha256 = "b7c59d64c61bab8ce93595b9a406c7a6d5107ab4361a42f8ccd7ad0f5f7004e7"
|
|
249
249
|
|
|
250
250
|
[tool.citation.packages.culture-agentirc-server-core.files."channel.py"]
|
|
251
251
|
status = "paraphrase"
|
|
@@ -302,3 +302,181 @@ sha256 = "574682bfc9f042e8c1a304db17cfb4b1e1034f384241b83aaaecfb64decb2271"
|
|
|
302
302
|
[tool.citation.packages.culture-agentirc-server-core.files."skills/icon.py"]
|
|
303
303
|
status = "paraphrase"
|
|
304
304
|
sha256 = "5ef97657599a37476b96aa59968373aed8c82024027297f8300be9886dc18d4a"
|
|
305
|
+
|
|
306
|
+
[tool.citation.packages.culture-tests-conftest]
|
|
307
|
+
schema = 2
|
|
308
|
+
source = "https://github.com/OriNachum/culture/blob/df50942/tests/conftest.py"
|
|
309
|
+
version = "df50942"
|
|
310
|
+
target = "tests/"
|
|
311
|
+
cited = "2026-05-01"
|
|
312
|
+
|
|
313
|
+
[tool.citation.packages.culture-tests-conftest.files."conftest.py"]
|
|
314
|
+
status = "paraphrase"
|
|
315
|
+
sha256 = "3ee6fd43e2833f28a75363503519d63a16fe6f89843471707bf684e544cac360"
|
|
316
|
+
|
|
317
|
+
[tool.citation.packages.culture-tests-server-core]
|
|
318
|
+
schema = 2
|
|
319
|
+
source = "https://github.com/OriNachum/culture/tree/df50942/tests"
|
|
320
|
+
version = "df50942"
|
|
321
|
+
target = "tests/"
|
|
322
|
+
cited = "2026-05-01"
|
|
323
|
+
|
|
324
|
+
[tool.citation.packages.culture-tests-server-core.files."test_channel.py"]
|
|
325
|
+
status = "quote"
|
|
326
|
+
sha256 = "ad335460aa043e9a9e3cce3e70d3ffef4ed0f8ae98138c6b8ec26e16044b1820"
|
|
327
|
+
|
|
328
|
+
[tool.citation.packages.culture-tests-server-core.files."test_connection.py"]
|
|
329
|
+
status = "paraphrase"
|
|
330
|
+
sha256 = "243205febd7cfdca5b129b1ce4e1952802aa68f6999e0dc807ce15ac068dc996"
|
|
331
|
+
|
|
332
|
+
[tool.citation.packages.culture-tests-server-core.files."test_discovery.py"]
|
|
333
|
+
status = "quote"
|
|
334
|
+
sha256 = "50d0dcad3f30f8b01215264147690786013e1753fae01cd682e6a415601394c8"
|
|
335
|
+
|
|
336
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_basic.py"]
|
|
337
|
+
status = "paraphrase"
|
|
338
|
+
sha256 = "bf1e9175a6167b24c13c0f9da2eff923e7e557a3cb99606d7829a14e63596187"
|
|
339
|
+
|
|
340
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_catalog.py"]
|
|
341
|
+
status = "quote"
|
|
342
|
+
sha256 = "4e986c18b98c6bd05232c7197c81873554225e3e7492db1b99986ac3039fec37"
|
|
343
|
+
|
|
344
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_federation.py"]
|
|
345
|
+
status = "quote"
|
|
346
|
+
sha256 = "f79cd2caf57c264243734bc3ce9cd1cd5543afc46ee28b0a1a6c94ae50efa86a"
|
|
347
|
+
|
|
348
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_history.py"]
|
|
349
|
+
status = "quote"
|
|
350
|
+
sha256 = "2148d1fbb951d5cf7f43d18088a92c268416ef5481837c37ca27cce3367e21db"
|
|
351
|
+
|
|
352
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_lifecycle.py"]
|
|
353
|
+
status = "quote"
|
|
354
|
+
sha256 = "b60dc1d4eed3c6c9d689ad2a9958eee8f095d1864fbe2a3bbd3dceea52a84728"
|
|
355
|
+
|
|
356
|
+
[tool.citation.packages.culture-tests-server-core.files."test_events_reserved_nick.py"]
|
|
357
|
+
status = "quote"
|
|
358
|
+
sha256 = "f387befe4df163861ec4b597c0fd330745107b3e36ba5604b69d872329418f50"
|
|
359
|
+
|
|
360
|
+
[tool.citation.packages.culture-tests-server-core.files."test_federation.py"]
|
|
361
|
+
status = "paraphrase"
|
|
362
|
+
sha256 = "1a1812cdbbf3adf026dc52c791ceb45b076b4eea7f0aa5f9f048a631628aee1f"
|
|
363
|
+
|
|
364
|
+
[tool.citation.packages.culture-tests-server-core.files."test_history.py"]
|
|
365
|
+
status = "quote"
|
|
366
|
+
sha256 = "066e674c847a54ec62fccd5bbaf1c524883e47e3867f9889279d48603b83ce4d"
|
|
367
|
+
|
|
368
|
+
[tool.citation.packages.culture-tests-server-core.files."test_link_reconnect.py"]
|
|
369
|
+
status = "paraphrase"
|
|
370
|
+
sha256 = "876330d0fa8db1dcabcc352c362e0b59c7b6a02bb689e61a8e1e464af3ecf058"
|
|
371
|
+
|
|
372
|
+
[tool.citation.packages.culture-tests-server-core.files."test_mentions.py"]
|
|
373
|
+
status = "quote"
|
|
374
|
+
sha256 = "2a0ec31bbace495fbee41813c319a3965396faaab24974fede2e8931d835b51e"
|
|
375
|
+
|
|
376
|
+
[tool.citation.packages.culture-tests-server-core.files."test_messaging.py"]
|
|
377
|
+
status = "quote"
|
|
378
|
+
sha256 = "0e6d500f721d5ee5eba0616caeb4822ffbd7bb6f7eb9287be2aa0fea7d7293c5"
|
|
379
|
+
|
|
380
|
+
[tool.citation.packages.culture-tests-server-core.files."test_modes.py"]
|
|
381
|
+
status = "quote"
|
|
382
|
+
sha256 = "35cd36a0171b824d638c89e021283843739eac8afc3dd846ec7e01115e6fd0e6"
|
|
383
|
+
|
|
384
|
+
[tool.citation.packages.culture-tests-server-core.files."test_room_persistence.py"]
|
|
385
|
+
status = "quote"
|
|
386
|
+
sha256 = "3c4e320dc459faccdf86ac67e5a33134fa5a14cc283fe1f6e79a8c09d281cd8c"
|
|
387
|
+
|
|
388
|
+
[tool.citation.packages.culture-tests-server-core.files."test_rooms_federation.py"]
|
|
389
|
+
status = "paraphrase"
|
|
390
|
+
sha256 = "d59f7c9cc95944d1e237fff99ee795f6281dcdccfefb39f4719f43f322085148"
|
|
391
|
+
|
|
392
|
+
[tool.citation.packages.culture-tests-server-core.files."test_rooms_integration.py"]
|
|
393
|
+
status = "quote"
|
|
394
|
+
sha256 = "9dcfb16639533c44bf3633c61b19f3cd75c549cea3cdac9b5624ecf7196ab3eb"
|
|
395
|
+
|
|
396
|
+
[tool.citation.packages.culture-tests-server-core.files."test_server_icon_skill.py"]
|
|
397
|
+
status = "quote"
|
|
398
|
+
sha256 = "c39526445af4861cab4b238ba27b00cc598cd0cf0312cea7e6d558eac62f6bd5"
|
|
399
|
+
|
|
400
|
+
[tool.citation.packages.culture-tests-server-core.files."test_skills.py"]
|
|
401
|
+
status = "quote"
|
|
402
|
+
sha256 = "55b4d18ec526282d87055409bcc6a497a8766d2cad28b67bd5d21df9af95149f"
|
|
403
|
+
|
|
404
|
+
[tool.citation.packages.culture-tests-server-core.files."test_threads.py"]
|
|
405
|
+
status = "quote"
|
|
406
|
+
sha256 = "0e31f65beea7521536c599b51fac36122eb9b42d50918d8c0bcb1d04d7f763bc"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
[tool.citation.packages.culture-tests-telemetry]
|
|
410
|
+
schema = 2
|
|
411
|
+
source = "https://github.com/OriNachum/culture/tree/df50942/tests/telemetry"
|
|
412
|
+
version = "df50942"
|
|
413
|
+
target = "tests/telemetry/"
|
|
414
|
+
cited = "2026-05-01"
|
|
415
|
+
|
|
416
|
+
[tool.citation.packages.culture-tests-telemetry.files."_fakes.py"]
|
|
417
|
+
status = "quote"
|
|
418
|
+
sha256 = "8358c340572006317fddadde2a07da75075f1426a3ded4cf04e4aa0b7c0f8b08"
|
|
419
|
+
|
|
420
|
+
[tool.citation.packages.culture-tests-telemetry.files."_metrics_helpers.py"]
|
|
421
|
+
status = "quote"
|
|
422
|
+
sha256 = "1f6a01ecaa8c3577ebdd58eedb7146532688e602a2e39f3a4f323f10b6aa6c4f"
|
|
423
|
+
|
|
424
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_audit_emit.py"]
|
|
425
|
+
status = "quote"
|
|
426
|
+
sha256 = "7b2c618443d5a5ff899ee830453537fb17256c5156c4a7242eee8017d557a2ed"
|
|
427
|
+
|
|
428
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_audit_lifecycle.py"]
|
|
429
|
+
status = "quote"
|
|
430
|
+
sha256 = "24e17aac1d31530ed77e69f59391c30f48c69427ded4a1b28330e04464e7f872"
|
|
431
|
+
|
|
432
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_audit_module.py"]
|
|
433
|
+
status = "quote"
|
|
434
|
+
sha256 = "e88c118abdf7f7f5241919a780417466ecf4f43180ee6dbf5253c9c05dece081"
|
|
435
|
+
|
|
436
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_audit_parse_error.py"]
|
|
437
|
+
status = "quote"
|
|
438
|
+
sha256 = "34cd91ef77811c5024f1534700b005605e706aaa7234bc22524aab85c272df3c"
|
|
439
|
+
|
|
440
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_config.py"]
|
|
441
|
+
status = "paraphrase"
|
|
442
|
+
sha256 = "150f9cb11d5737f154f0f85f8e7e65135482ad86010ca2386215f9a462ad2474"
|
|
443
|
+
|
|
444
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_dispatch_span.py"]
|
|
445
|
+
status = "quote"
|
|
446
|
+
sha256 = "a5116e92d1d88da18f2cd5d026b69c0efbcdf3845c3da4c5f3177dfa46ade7fb"
|
|
447
|
+
|
|
448
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_emit_event_span.py"]
|
|
449
|
+
status = "quote"
|
|
450
|
+
sha256 = "5fb0cf247669717f1add5392d18d3a5639d35d3350982b521a40e3ad031bc7ab"
|
|
451
|
+
|
|
452
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_metrics_init.py"]
|
|
453
|
+
status = "quote"
|
|
454
|
+
sha256 = "bfe2b1166032ec027a0ab06b3124d31a9b9deccb431d0550e14fb83f0a778f21"
|
|
455
|
+
|
|
456
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_metrics_s2s.py"]
|
|
457
|
+
status = "paraphrase"
|
|
458
|
+
sha256 = "b07815fba360afda871fb2ed2996087b5778d912027751da264e188e87a7b41f"
|
|
459
|
+
|
|
460
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_outbound_inject.py"]
|
|
461
|
+
status = "quote"
|
|
462
|
+
sha256 = "1b2a5132dc1b02cb19ca9aa3edc951c2d0431ff9a26e649175eef72faafb7168"
|
|
463
|
+
|
|
464
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_parse_error.py"]
|
|
465
|
+
status = "quote"
|
|
466
|
+
sha256 = "5cfeffe6ccb3b18e1d78669086cc9e6e1ac473f8929eb1d1bcd15523851dd284"
|
|
467
|
+
|
|
468
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_s2s_relay_span.py"]
|
|
469
|
+
status = "paraphrase"
|
|
470
|
+
sha256 = "a2802230c8742b4a94e27f4854b81803119a41a5ca245d92d67001c0e2356489"
|
|
471
|
+
|
|
472
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_server_init.py"]
|
|
473
|
+
status = "quote"
|
|
474
|
+
sha256 = "16ad131fdb48a8ff0162227f68d0b50ac4f9dc783dbd0d8061112613efff72f5"
|
|
475
|
+
|
|
476
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_server_link_inject.py"]
|
|
477
|
+
status = "paraphrase"
|
|
478
|
+
sha256 = "ce2de77e1002758976e7ac50bd61cbc8a551e128b285eb01e4a1cf23ce3aa148"
|
|
479
|
+
|
|
480
|
+
[tool.citation.packages.culture-tests-telemetry.files."test_tracing.py"]
|
|
481
|
+
status = "paraphrase"
|
|
482
|
+
sha256 = "1847662286c1d15848ae3624f61c88f78f1f5d16ab30af3e77981c94e9e5c86d"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Shared test helpers, agentirc-native (NOT vendored from culture).
|
|
2
|
+
|
|
3
|
+
Extracted to address SonarCloud's >3% duplication threshold. The
|
|
4
|
+
boot-linked-pair pattern repeats verbatim across the cited
|
|
5
|
+
``test_federation.py`` (5 sites), ``test_link_reconnect.py`` (3
|
|
6
|
+
sites), and the ``linked_servers`` conftest fixture; CPD flags it as
|
|
7
|
+
~22 lines × 9 = ~200 lines of duplicated test scaffold.
|
|
8
|
+
|
|
9
|
+
Why these tests can't share the existing ``linked_servers`` pytest
|
|
10
|
+
fixture: each one needs to drive the boot/link/teardown lifecycle
|
|
11
|
+
itself — duplicate-link rejection, slow-link teardown, mid-test
|
|
12
|
+
peer kill, backfill replay — and the function-scoped fixture starts
|
|
13
|
+
both servers + completes the handshake before the test body runs.
|
|
14
|
+
|
|
15
|
+
Re-snapshotting from a future culture commit means re-running the
|
|
16
|
+
same extraction: replace each ~22-line inline boot block with a
|
|
17
|
+
single ``boot_linked_pair(tmp_path)`` call. Mechanical sed pattern.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
from agentirc.config import LinkConfig, ServerConfig, TelemetryConfig
|
|
25
|
+
from agentirc.ircd import IRCd
|
|
26
|
+
|
|
27
|
+
from tests.conftest import TEST_LINK_PASSWORD
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def boot_linked_pair(
|
|
31
|
+
tmp_path,
|
|
32
|
+
*,
|
|
33
|
+
link_password: str = TEST_LINK_PASSWORD,
|
|
34
|
+
with_audit: bool = False,
|
|
35
|
+
webhook_port: int | None = None,
|
|
36
|
+
) -> tuple[IRCd, IRCd]:
|
|
37
|
+
"""Boot two IRCd instances pre-wired with link configs.
|
|
38
|
+
|
|
39
|
+
Returns ``(server_a, server_b)`` after both ``start()`` calls
|
|
40
|
+
have returned with OS-assigned ports populated. Does NOT drive
|
|
41
|
+
the S2S handshake — caller decides whether to call
|
|
42
|
+
:func:`link_pair` or invoke ``connect_to_peer`` manually.
|
|
43
|
+
|
|
44
|
+
``with_audit=True`` plumbs ``TelemetryConfig(audit_dir=...)``
|
|
45
|
+
onto each side, isolating audit JSONL writes to ``tmp_path``.
|
|
46
|
+
``webhook_port=0`` suppresses the webhook listener — the
|
|
47
|
+
``linked_servers`` fixture sets this; the inline sites do not.
|
|
48
|
+
"""
|
|
49
|
+
kwargs_a: dict = {"links": [
|
|
50
|
+
LinkConfig(name="beta", host="127.0.0.1", port=0, password=link_password)
|
|
51
|
+
]}
|
|
52
|
+
kwargs_b: dict = {"links": [
|
|
53
|
+
LinkConfig(name="alpha", host="127.0.0.1", port=0, password=link_password)
|
|
54
|
+
]}
|
|
55
|
+
if with_audit:
|
|
56
|
+
kwargs_a["telemetry"] = TelemetryConfig(audit_dir=str(tmp_path / "audit_alpha"))
|
|
57
|
+
kwargs_b["telemetry"] = TelemetryConfig(audit_dir=str(tmp_path / "audit_beta"))
|
|
58
|
+
if webhook_port is not None:
|
|
59
|
+
kwargs_a["webhook_port"] = webhook_port
|
|
60
|
+
kwargs_b["webhook_port"] = webhook_port
|
|
61
|
+
|
|
62
|
+
config_a = ServerConfig(name="alpha", host="127.0.0.1", port=0, **kwargs_a)
|
|
63
|
+
config_b = ServerConfig(name="beta", host="127.0.0.1", port=0, **kwargs_b)
|
|
64
|
+
server_a = IRCd(config_a)
|
|
65
|
+
server_b = IRCd(config_b)
|
|
66
|
+
await server_a.start()
|
|
67
|
+
await server_b.start()
|
|
68
|
+
# `_server` is set by `start()` — assert for type-checker / SonarCloud
|
|
69
|
+
# S2259, since the IRCd type carries `_server: asyncio.AbstractServer | None`.
|
|
70
|
+
assert server_a._server is not None
|
|
71
|
+
assert server_b._server is not None
|
|
72
|
+
server_a.config.port = server_a._server.sockets[0].getsockname()[1]
|
|
73
|
+
server_b.config.port = server_b._server.sockets[0].getsockname()[1]
|
|
74
|
+
return server_a, server_b
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def link_pair(
|
|
78
|
+
server_a: IRCd,
|
|
79
|
+
server_b: IRCd,
|
|
80
|
+
*,
|
|
81
|
+
link_password: str = TEST_LINK_PASSWORD,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Drive the S2S handshake on a booted linked-pair.
|
|
84
|
+
|
|
85
|
+
Updates each side's ``LinkConfig`` with the actual OS-assigned
|
|
86
|
+
port (the ``links=[…port=0]`` placeholder gets resolved here),
|
|
87
|
+
calls ``connect_to_peer`` from A → B, and polls up to ~2.5s for
|
|
88
|
+
both sides to see the link before returning.
|
|
89
|
+
"""
|
|
90
|
+
server_a.config.links[0].port = server_b.config.port
|
|
91
|
+
server_b.config.links[0].port = server_a.config.port
|
|
92
|
+
await server_a.connect_to_peer("127.0.0.1", server_b.config.port, link_password)
|
|
93
|
+
for _ in range(50):
|
|
94
|
+
if "beta" in server_a.links and "alpha" in server_b.links:
|
|
95
|
+
break
|
|
96
|
+
await asyncio.sleep(0.05)
|