agentirc-cli 9.12.0__tar.gz → 9.13.1__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.12.0 → agentirc_cli-9.13.1}/.github/workflows/tests.yml +18 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/CHANGELOG.md +33 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/PKG-INFO +8 -2
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/README.md +6 -0
- agentirc_cli-9.13.1/TRADEMARKS.md +38 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/protocol/replies.py +5 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/client.py +19 -2
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/protocol.py +6 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/api-stability.md +28 -1
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/extension-api.md +1 -1
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/pyproject.toml +1 -1
- agentirc_cli-9.13.1/scripts/sonar-pr-issues.py +168 -0
- agentirc_cli-9.13.1/sonar-project.properties +6 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_liveness.py +2 -2
- agentirc_cli-9.13.1/tests/test_motd.py +69 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_presence_stale.py +2 -2
- agentirc_cli-9.13.1/tests/test_sonar_pr_issues.py +128 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/uv.lock +2 -2
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/communicate/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/recall/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/recall/scripts/recall.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/remember/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/remember/scripts/remember.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/think/SKILL.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills/think/scripts/think.sh +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.claude/skills.local.yaml.example +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/current +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/current_plan +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/frames/agentirc-9-7-0-ships-an-embedded-bot-framework-bot.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/frames/agentirc-now-speaks-presence-the-ircd-parses-resid.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/frames/agentirc-ships-an-agent-accessibility-release-ai-a.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/plans/agentirc-9-7-0-ships-an-embedded-bot-framework-bot.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/plans/agentirc-now-speaks-presence-the-ircd-parses-resid.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.devague/plans/agentirc-ships-an-agent-accessibility-release-ai-a.json +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/.gitignore +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/CLAUDE.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/LICENSE +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/__main__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/aio.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/bots/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/bots/bot_manager.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/bots/http_listener.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/cli_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/cli_shared/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/cli_shared/constants.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/cli_shared/mesh.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/constants.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/event_subscriptions.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/pidfile.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/protocol/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/protocol/message.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/telemetry/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/telemetry/audit.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/telemetry/context.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/telemetry/metrics.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/telemetry/tracing.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/_internal/virtual_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/agent_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/bot.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/bot_manager.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/cli.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/config.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/filter_dsl.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/http_listener.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/template_engine.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/bots/virtual_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/channel.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/cli.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/config.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/events.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/history_store.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/ircd.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/remote_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/room_store.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/rooms_util.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/server_link.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skill.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/history.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/icon.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/presence.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/skills/threads.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/thread_store.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/agentirc/virtual_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/agent-walkthrough.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/bots.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/cli.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/deployment.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/plans/2026-06-12-agentirc-9-7-0-ships-an-embedded-bot-framework-bot.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/plans/2026-07-01-agentirc-ships-an-agent-accessibility-release-ai-a.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/plans/2026-07-07-agentirc-now-speaks-presence-the-ircd-parses-resid.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/specs/2026-06-12-agentirc-9-7-0-ships-an-embedded-bot-framework-bot.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/specs/2026-07-01-agentirc-ships-an-agent-accessibility-release-ai-a.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/specs/2026-07-02-agent-accessibility-gap-verification.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/specs/2026-07-02-agent-accessibility-release-audit.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/specs/2026-07-07-agentirc-now-speaks-presence-the-ircd-parses-resid.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/steward/onboarding.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/superpowers/specs/2026-04-30-bootstrap-design.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/superpowers/specs/2026-05-01-bot-extension-api-design.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/docs/superpowers/specs/2026-05-01-task14-audit.md +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/_helpers.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_bot.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_bot_host.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_bot_manager.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_cli_bot.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_config_bots.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_filter_dsl.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_http_listener.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_public_surface.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/bots/test_template_engine.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/conftest.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/__init__.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/_fakes.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/_metrics_helpers.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_audit_emit.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_audit_lifecycle.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_audit_module.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_audit_parse_error.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_config.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_dispatch_span.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_emit_event_span.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_metrics_init.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_metrics_s2s.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_outbound_inject.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_parse_error.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_s2s_relay_span.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_server_init.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_server_link_inject.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/telemetry/test_tracing.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_agent_client.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_agent_walkthrough.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_api_stability_embedding.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_bot_capability.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_channel.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_cli.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_cli_client_verbs.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_cli_json.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_client_backfill.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_config_loader.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_connection.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_discovery.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_disk_paths.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_dm_history.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_docs_promises.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_error_tokens.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_event_subscriptions.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_eventpub.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_basic.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_catalog.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_federation.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_history.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_lifecycle.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_events_reserved_nick.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_federation.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_history.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_history_since.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_long_messages.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_mentions.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_message_tags.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_messaging.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_modes.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_presence.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_presence_federation.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_protocol_bot_exports.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_protocol_presence.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_reconnect_catchup.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_skills.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_threads.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_verb_discovery.py +0 -0
- {agentirc_cli-9.12.0 → agentirc_cli-9.13.1}/tests/test_wire_format_envelope.py +0 -0
|
@@ -24,6 +24,11 @@ jobs:
|
|
|
24
24
|
runs-on: ubuntu-latest
|
|
25
25
|
permissions:
|
|
26
26
|
contents: read
|
|
27
|
+
# A presence flag, not the secret: `secrets.*` isn't allowed in `if:`.
|
|
28
|
+
# Fork and Dependabot PRs get no SONAR_TOKEN, so the SonarCloud gate skips
|
|
29
|
+
# there instead of waiting out its 300 s timeout.
|
|
30
|
+
env:
|
|
31
|
+
SONAR_TOKEN_PRESENT: ${{ secrets.SONAR_TOKEN != '' }}
|
|
27
32
|
steps:
|
|
28
33
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
29
34
|
|
|
@@ -57,6 +62,19 @@ jobs:
|
|
|
57
62
|
UV_PROJECT_ENVIRONMENT: .venv
|
|
58
63
|
run: uv run pytest -n auto -v
|
|
59
64
|
|
|
65
|
+
# No unresolved SonarCloud issue on the PR. SonarCloud's automatic analysis
|
|
66
|
+
# (the GitHub App) scans the PR; a code smell leaves its quality gate green,
|
|
67
|
+
# so this fails the job while the PR has any OPEN or CONFIRMED issue.
|
|
68
|
+
# ACCEPTED and FALSE_POSITIVE ones pass. Cited from culture-rules (#43).
|
|
69
|
+
- name: SonarCloud unresolved issues
|
|
70
|
+
if: steps.bootstrap.outputs.ready == 'true' && env.SONAR_TOKEN_PRESENT == 'true' && github.event_name == 'pull_request'
|
|
71
|
+
env:
|
|
72
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
73
|
+
SONAR_HOST_URL: https://sonarcloud.io
|
|
74
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
75
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
76
|
+
run: python3 scripts/sonar-pr-issues.py
|
|
77
|
+
|
|
60
78
|
version-check:
|
|
61
79
|
needs: lint
|
|
62
80
|
runs-on: ubuntu-latest
|
|
@@ -4,6 +4,39 @@ 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.13.1] - 2026-10-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- CI fails a pull request that has an unresolved SonarCloud issue: after
|
|
12
|
+
SonarCloud's automatic analysis of the PR's head commit,
|
|
13
|
+
`scripts/sonar-pr-issues.py` (cited from culture-rules) fails on any OPEN or
|
|
14
|
+
CONFIRMED issue. ACCEPTED and FALSE_POSITIVE issues pass. Same gate as
|
|
15
|
+
culture, irc-lens and culture-library. `sonar-project.properties` names the
|
|
16
|
+
project key for it.
|
|
17
|
+
|
|
18
|
+
## [9.13.0] - 2026-10-30
|
|
19
|
+
|
|
20
|
+
First agentirc release that presents itself as CULTURE.DEV on the wire —
|
|
21
|
+
first-use evidence for the IRC surface of the CULTURE.DEV trademark (culture
|
|
22
|
+
spec `docs/specs/2026-10-30-culture-dev-on-all-public-surfaces.md`).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- MOTD: registration now ends with `375`/`372`/`376` carrying a fixed
|
|
27
|
+
CULTURE.DEV message of the day, and the `MOTD` command (registered clients)
|
|
28
|
+
replays it. `RPL_MOTDSTART`, `RPL_MOTD` and `RPL_ENDOFMOTD`
|
|
29
|
+
constants added; no `422` path since a MOTD is always present.
|
|
30
|
+
- `TRADEMARKS.md` — CULTURE.DEV is a trademark of Ori Nachum; Apache-2.0
|
|
31
|
+
grants no trademark rights.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- `001` now reads `Welcome to CULTURE.DEV on <server> <prefix>` and `002`
|
|
36
|
+
reads `Your host is <server>, running CULTURE.DEV`. The `004` version token
|
|
37
|
+
(`culture`) and the `culture.dev/traceparent` / `tracestate` tags are
|
|
38
|
+
unchanged.
|
|
39
|
+
|
|
7
40
|
## [9.12.0] - 2026-07-07
|
|
8
41
|
|
|
9
42
|
The PRESENCE release: the IRCd now parses resident presence heartbeats,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: agentirc-cli
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.13.1
|
|
4
4
|
Summary: Agent-friendly IRCd: server core for AI agent meshes
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/agentirc
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/agentirc/issues
|
|
@@ -356,3 +356,9 @@ Dev setup: `uv venv && uv pip install -e ".[dev]"`, then `pytest -n auto`
|
|
|
356
356
|
## License
|
|
357
357
|
|
|
358
358
|
Apache 2.0 — see [`LICENSE`](LICENSE).
|
|
359
|
+
|
|
360
|
+
## Trademarks
|
|
361
|
+
|
|
362
|
+
CULTURE.DEV is a trademark of Ori Nachum. agentirc greets clients as the
|
|
363
|
+
CULTURE.DEV server; the Apache 2.0 license does not grant trademark rights —
|
|
364
|
+
see [`TRADEMARKS.md`](TRADEMARKS.md).
|
|
@@ -317,3 +317,9 @@ Dev setup: `uv venv && uv pip install -e ".[dev]"`, then `pytest -n auto`
|
|
|
317
317
|
## License
|
|
318
318
|
|
|
319
319
|
Apache 2.0 — see [`LICENSE`](LICENSE).
|
|
320
|
+
|
|
321
|
+
## Trademarks
|
|
322
|
+
|
|
323
|
+
CULTURE.DEV is a trademark of Ori Nachum. agentirc greets clients as the
|
|
324
|
+
CULTURE.DEV server; the Apache 2.0 license does not grant trademark rights —
|
|
325
|
+
see [`TRADEMARKS.md`](TRADEMARKS.md).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Trademarks
|
|
2
|
+
|
|
3
|
+
CULTURE.DEV is a trademark of Ori Nachum. A US trademark application for
|
|
4
|
+
the mark is pending; it is marked CULTURE.DEV™ until registration.
|
|
5
|
+
|
|
6
|
+
CULTURE.DEV names the product and service available at
|
|
7
|
+
[culture.dev](https://culture.dev) and
|
|
8
|
+
[chat.culture.dev](https://chat.culture.dev): the CULTURE.DEV CLI
|
|
9
|
+
(installed with `uv tool install culture`), the CULTURE.DEV live chat, and
|
|
10
|
+
the software, documentation, and agent mesh behind them.
|
|
11
|
+
|
|
12
|
+
## Trademarks are not licensed with the code
|
|
13
|
+
|
|
14
|
+
The source code and documentation in this repository are licensed under the
|
|
15
|
+
Apache License 2.0 (see `LICENSE`). Section 6 of that license states that it
|
|
16
|
+
does not grant permission to use the licensor's trade names, trademarks,
|
|
17
|
+
service marks, or product names. The same applies here: no trademark
|
|
18
|
+
license, and no right to use the CULTURE.DEV name or logos or to imply
|
|
19
|
+
affiliation or endorsement, is granted unless explicitly stated in writing.
|
|
20
|
+
|
|
21
|
+
The same reservation covers the related names AgentCulture and AgentIRC and
|
|
22
|
+
the CULTURE.DEV logos and badges.
|
|
23
|
+
|
|
24
|
+
## What you can do
|
|
25
|
+
|
|
26
|
+
- Refer to CULTURE.DEV by name to describe it truthfully, for example "works
|
|
27
|
+
with CULTURE.DEV" or "a fork of the CULTURE.DEV CLI".
|
|
28
|
+
- Keep the command and package names (`culture`, `agentirc-cli`, `irc-lens`)
|
|
29
|
+
when you redistribute the code under its license.
|
|
30
|
+
|
|
31
|
+
## What needs written permission
|
|
32
|
+
|
|
33
|
+
- Using CULTURE.DEV, or a confusingly similar name or logo, as the name of
|
|
34
|
+
your own product, service, domain, or organization.
|
|
35
|
+
- Suggesting that your product, fork, or service is made, sponsored, or
|
|
36
|
+
endorsed by CULTURE.DEV.
|
|
37
|
+
|
|
38
|
+
Questions: [inbox@culture.dev](mailto:inbox@culture.dev).
|
|
@@ -521,11 +521,11 @@ class Client:
|
|
|
521
521
|
async def _send_welcome(self) -> None:
|
|
522
522
|
await self.send_numeric(
|
|
523
523
|
replies.RPL_WELCOME,
|
|
524
|
-
f"Welcome to {self.server.config.name}
|
|
524
|
+
f"Welcome to CULTURE.DEV on {self.server.config.name} {self.prefix}",
|
|
525
525
|
)
|
|
526
526
|
await self.send_numeric(
|
|
527
527
|
replies.RPL_YOURHOST,
|
|
528
|
-
f"Your host is {self.server.config.name}, running
|
|
528
|
+
f"Your host is {self.server.config.name}, running CULTURE.DEV",
|
|
529
529
|
)
|
|
530
530
|
await self.send_numeric(
|
|
531
531
|
replies.RPL_CREATED,
|
|
@@ -538,6 +538,23 @@ class Client:
|
|
|
538
538
|
"o",
|
|
539
539
|
"ov",
|
|
540
540
|
)
|
|
541
|
+
await self._send_motd()
|
|
542
|
+
|
|
543
|
+
async def _send_motd(self) -> None:
|
|
544
|
+
"""Send the fixed CULTURE.DEV MOTD (375, 372 per line, 376)."""
|
|
545
|
+
name = self.server.config.name
|
|
546
|
+
await self.send_numeric(replies.RPL_MOTDSTART, f"- {name} Message of the day -")
|
|
547
|
+
for line in (
|
|
548
|
+
f"- {name} - CULTURE.DEV",
|
|
549
|
+
"- The professional workspace for agents. https://culture.dev",
|
|
550
|
+
):
|
|
551
|
+
await self.send_numeric(replies.RPL_MOTD, line)
|
|
552
|
+
await self.send_numeric(replies.RPL_ENDOFMOTD, "End of /MOTD command")
|
|
553
|
+
|
|
554
|
+
async def _handle_motd(self, msg: Message) -> None:
|
|
555
|
+
if not self._registered:
|
|
556
|
+
return
|
|
557
|
+
await self._send_motd()
|
|
541
558
|
|
|
542
559
|
async def _handle_join(self, msg: Message) -> None:
|
|
543
560
|
if not self._registered:
|
|
@@ -77,12 +77,15 @@ from agentirc._internal.protocol.replies import (
|
|
|
77
77
|
ERR_USERSDONTMATCH,
|
|
78
78
|
RPL_CHANNELMODEIS,
|
|
79
79
|
RPL_CREATED,
|
|
80
|
+
RPL_ENDOFMOTD,
|
|
80
81
|
RPL_ENDOFNAMES,
|
|
81
82
|
RPL_ENDOFWHO,
|
|
82
83
|
RPL_ENDOFWHOIS,
|
|
83
84
|
RPL_LIST,
|
|
84
85
|
RPL_LISTEND,
|
|
85
86
|
RPL_LISTSTART,
|
|
87
|
+
RPL_MOTD,
|
|
88
|
+
RPL_MOTDSTART,
|
|
86
89
|
RPL_MYINFO,
|
|
87
90
|
RPL_NAMREPLY,
|
|
88
91
|
RPL_NOTOPIC,
|
|
@@ -421,12 +424,15 @@ __all__ = [
|
|
|
421
424
|
"ERR_USERSDONTMATCH",
|
|
422
425
|
"RPL_CHANNELMODEIS",
|
|
423
426
|
"RPL_CREATED",
|
|
427
|
+
"RPL_ENDOFMOTD",
|
|
424
428
|
"RPL_ENDOFNAMES",
|
|
425
429
|
"RPL_ENDOFWHO",
|
|
426
430
|
"RPL_ENDOFWHOIS",
|
|
427
431
|
"RPL_LIST",
|
|
428
432
|
"RPL_LISTEND",
|
|
429
433
|
"RPL_LISTSTART",
|
|
434
|
+
"RPL_MOTD",
|
|
435
|
+
"RPL_MOTDSTART",
|
|
430
436
|
"RPL_MYINFO",
|
|
431
437
|
"RPL_NAMREPLY",
|
|
432
438
|
"RPL_NOTOPIC",
|
|
@@ -595,11 +595,38 @@ About 40 module-level uppercase string constants:
|
|
|
595
595
|
|
|
596
596
|
### Numeric reply codes
|
|
597
597
|
|
|
598
|
-
Re-exported from `agentirc._internal.protocol.replies`. About
|
|
598
|
+
Re-exported from `agentirc._internal.protocol.replies`. About 36 names:
|
|
599
599
|
`ERR_*` (`ERR_ALREADYREGISTRED`, `ERR_NEEDMOREPARAMS`,
|
|
600
600
|
`ERR_NICKNAMEINUSE`, `ERR_NOSUCHCHANNEL`, …) and `RPL_*`
|
|
601
601
|
(`RPL_WELCOME`, `RPL_YOURHOST`, `RPL_CREATED`, `RPL_MYINFO`, …).
|
|
602
602
|
|
|
603
|
+
#### Greeting and MOTD (CULTURE.DEV)
|
|
604
|
+
|
|
605
|
+
agentirc is the IRCd of the CULTURE.DEV mesh, and its greeting says so. On
|
|
606
|
+
registration it sends, in order:
|
|
607
|
+
|
|
608
|
+
```text
|
|
609
|
+
:srv 001 nick :Welcome to CULTURE.DEV on srv nick!user@host
|
|
610
|
+
:srv 002 nick :Your host is srv, running CULTURE.DEV
|
|
611
|
+
:srv 003 nick :This server was created today
|
|
612
|
+
:srv 004 nick srv culture o ov
|
|
613
|
+
:srv 375 nick :- srv Message of the day -
|
|
614
|
+
:srv 372 nick :- srv - CULTURE.DEV
|
|
615
|
+
:srv 372 nick :- The professional workspace for agents. https://culture.dev
|
|
616
|
+
:srv 376 nick :End of /MOTD command
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
The `MOTD` command (registered clients only) replays 375/372/376. The text is
|
|
620
|
+
fixed in code (`Client._send_motd`); there is no config override yet,
|
|
621
|
+
and no 422 (no MOTD) reply since a MOTD is always present. `MOTD <target>`
|
|
622
|
+
ignores the target and replays the local MOTD. Protocol
|
|
623
|
+
strings use plain `CULTURE.DEV` with no trademark sign. The `004` version
|
|
624
|
+
token stays `culture` and the `culture.dev/traceparent` / `culture.dev/tracestate`
|
|
625
|
+
message tags are unchanged: these are wire identifiers, not branding. Clients
|
|
626
|
+
should parse `001` by its last token (the nick!user@host prefix), which is
|
|
627
|
+
unchanged. `RPL_MOTDSTART`, `RPL_MOTD` and `RPL_ENDOFMOTD` are exported from
|
|
628
|
+
`agentirc.protocol`.
|
|
629
|
+
|
|
603
630
|
### IRCv3 / extension tag names
|
|
604
631
|
|
|
605
632
|
Re-exported from `agentirc._internal.telemetry.context`:
|
|
@@ -61,7 +61,7 @@ S: :server CAP * ACK :agentirc.io/bot message-tags
|
|
|
61
61
|
C: CAP END
|
|
62
62
|
C: NICK mybot
|
|
63
63
|
C: USER mybot 0 * :My Bot
|
|
64
|
-
S: :server 001 mybot :Welcome to
|
|
64
|
+
S: :server 001 mybot :Welcome to CULTURE.DEV on server mybot!mybot@127.0.0.1
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Most bots will request both `agentirc.io/bot` (silent presence + EVENTSUB
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Cited from agentculture/culture-rules@48942c3 scripts/sonar-pr-issues.py (cite, don't import).
|
|
3
|
+
"""Fail a pull request that has unresolved SonarCloud issues.
|
|
4
|
+
|
|
5
|
+
SonarCloud's quality gate can pass with open issues on new code (a code smell does not
|
|
6
|
+
turn it red), so CI runs this right after the scan: it waits until SonarCloud's analysis
|
|
7
|
+
of the PR is for the PR's head commit, then lists the PR's issues whose status is OPEN or
|
|
8
|
+
CONFIRMED and exits 1 when there is any. ACCEPTED and FALSE_POSITIVE issues are resolved
|
|
9
|
+
by a person and pass; FIXED ones are gone.
|
|
10
|
+
|
|
11
|
+
Environment: ``SONAR_HOST_URL`` (the server; defaults to localhost so nothing outside is
|
|
12
|
+
called by accident), ``SONAR_PROJECT_KEY`` (else ``sonar.projectKey`` from
|
|
13
|
+
``sonar-project.properties``), ``PR_NUMBER``, ``HEAD_SHA`` and optionally ``SONAR_TOKEN``.
|
|
14
|
+
Exit codes: 0 no unresolved issue, 1 unresolved issues, 2 the analysis or the API could
|
|
15
|
+
not be read. Standard library only.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import base64
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
import urllib.parse
|
|
26
|
+
import urllib.request
|
|
27
|
+
from collections.abc import Callable, Mapping
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
UNRESOLVED = ("OPEN", "CONFIRMED")
|
|
32
|
+
PAGE_SIZE = 100
|
|
33
|
+
WAIT_S = 300
|
|
34
|
+
POLL_S = 10
|
|
35
|
+
|
|
36
|
+
Fetch = Callable[[str, Mapping[str, str]], Mapping[str, Any]]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def project_key(env: Mapping[str, str], root: Path) -> str:
|
|
40
|
+
"""``SONAR_PROJECT_KEY``, else ``sonar.projectKey`` from ``sonar-project.properties``."""
|
|
41
|
+
if env.get("SONAR_PROJECT_KEY"):
|
|
42
|
+
return env["SONAR_PROJECT_KEY"]
|
|
43
|
+
for line in (root / "sonar-project.properties").read_text().splitlines():
|
|
44
|
+
name, _, value = line.partition("=")
|
|
45
|
+
if name.strip() == "sonar.projectKey" and value.strip():
|
|
46
|
+
return value.strip()
|
|
47
|
+
raise SystemExit("sonar-pr-issues: no SONAR_PROJECT_KEY and no sonar.projectKey")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def http_fetch(host: str, token: str | None) -> Fetch:
|
|
51
|
+
"""A GET of ``host + path`` with ``params``, as JSON (token as basic auth, if any)."""
|
|
52
|
+
|
|
53
|
+
def fetch(path: str, params: Mapping[str, str]) -> Mapping[str, Any]:
|
|
54
|
+
url = f"{host.rstrip('/')}{path}?{urllib.parse.urlencode(params)}"
|
|
55
|
+
req = urllib.request.Request(url) # noqa: S310 - the host is the configured server
|
|
56
|
+
if token:
|
|
57
|
+
basic = base64.b64encode(f"{token}:".encode()).decode()
|
|
58
|
+
req.add_header("Authorization", f"Basic {basic}")
|
|
59
|
+
with urllib.request.urlopen(req, timeout=30) as resp: # nosec B310
|
|
60
|
+
return json.load(resp)
|
|
61
|
+
|
|
62
|
+
return fetch
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def analysed_commit(fetch: Fetch, project: str, pr: str) -> str | None:
|
|
66
|
+
"""The commit SonarCloud last analysed for pull request ``pr``, or ``None``."""
|
|
67
|
+
data = fetch("/api/project_pull_requests/list", {"project": project})
|
|
68
|
+
for item in data.get("pullRequests") or ():
|
|
69
|
+
if str(item.get("key")) == pr:
|
|
70
|
+
commit = item.get("commit") or {}
|
|
71
|
+
sha = commit.get("sha")
|
|
72
|
+
return sha if isinstance(sha, str) else None
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def wait_for_analysis(
|
|
77
|
+
fetch: Fetch,
|
|
78
|
+
project: str,
|
|
79
|
+
pr: str,
|
|
80
|
+
head: str,
|
|
81
|
+
*,
|
|
82
|
+
wait_s: float = WAIT_S,
|
|
83
|
+
poll_s: float = POLL_S,
|
|
84
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
85
|
+
clock: Callable[[], float] = time.monotonic,
|
|
86
|
+
) -> bool:
|
|
87
|
+
"""True once the PR's analysis is for ``head``; False when ``wait_s`` runs out."""
|
|
88
|
+
deadline = clock() + wait_s
|
|
89
|
+
while True:
|
|
90
|
+
if analysed_commit(fetch, project, pr) == head:
|
|
91
|
+
return True
|
|
92
|
+
if clock() >= deadline:
|
|
93
|
+
return False
|
|
94
|
+
sleep(poll_s)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def unresolved_issues(fetch: Fetch, project: str, pr: str) -> list[Mapping[str, Any]]:
|
|
98
|
+
"""Every OPEN or CONFIRMED issue of pull request ``pr``, across pages."""
|
|
99
|
+
out: list[Mapping[str, Any]] = []
|
|
100
|
+
page = 1
|
|
101
|
+
while True:
|
|
102
|
+
data = fetch(
|
|
103
|
+
"/api/issues/search",
|
|
104
|
+
{
|
|
105
|
+
"componentKeys": project,
|
|
106
|
+
"pullRequest": pr,
|
|
107
|
+
"issueStatuses": ",".join(UNRESOLVED),
|
|
108
|
+
"ps": str(PAGE_SIZE),
|
|
109
|
+
"p": str(page),
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
issues = [i for i in data.get("issues") or () if isinstance(i, Mapping)]
|
|
113
|
+
out.extend(issues)
|
|
114
|
+
total = data.get("total")
|
|
115
|
+
if not issues or not isinstance(total, int) or len(out) >= total:
|
|
116
|
+
return out
|
|
117
|
+
page += 1
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def describe(issue: Mapping[str, Any], project: str) -> str:
|
|
121
|
+
"""One line: rule, file and line, severity, message."""
|
|
122
|
+
component = str(issue.get("component") or "").removeprefix(f"{project}:")
|
|
123
|
+
line = issue.get("line")
|
|
124
|
+
where = f"{component}:{line}" if line else component
|
|
125
|
+
return (
|
|
126
|
+
f"[{issue.get('rule')}] {where} ({issue.get('severity')}, "
|
|
127
|
+
f"{issue.get('issueStatus') or issue.get('status')}): {issue.get('message')}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main(
|
|
132
|
+
env: Mapping[str, str] | None = None,
|
|
133
|
+
fetch: Fetch | None = None,
|
|
134
|
+
root: Path | None = None,
|
|
135
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
136
|
+
) -> int:
|
|
137
|
+
env = os.environ if env is None else env
|
|
138
|
+
root = Path(__file__).resolve().parents[1] if root is None else root
|
|
139
|
+
pr, head = env.get("PR_NUMBER", ""), env.get("HEAD_SHA", "")
|
|
140
|
+
if not pr or not head:
|
|
141
|
+
print("sonar-pr-issues: PR_NUMBER and HEAD_SHA are required", file=sys.stderr)
|
|
142
|
+
return 2
|
|
143
|
+
project = project_key(env, root)
|
|
144
|
+
if fetch is None:
|
|
145
|
+
host = env.get("SONAR_HOST_URL") or "http://localhost:9000"
|
|
146
|
+
fetch = http_fetch(host, env.get("SONAR_TOKEN") or None)
|
|
147
|
+
try:
|
|
148
|
+
if not wait_for_analysis(fetch, project, pr, head, sleep=sleep):
|
|
149
|
+
print(
|
|
150
|
+
f"sonar-pr-issues: no SonarCloud analysis of PR #{pr} at {head[:12]}",
|
|
151
|
+
file=sys.stderr,
|
|
152
|
+
)
|
|
153
|
+
return 2
|
|
154
|
+
issues = unresolved_issues(fetch, project, pr)
|
|
155
|
+
except (OSError, ValueError) as exc: # URLError is an OSError; bad JSON a ValueError
|
|
156
|
+
print(f"sonar-pr-issues: SonarCloud could not be read: {exc}", file=sys.stderr)
|
|
157
|
+
return 2
|
|
158
|
+
if not issues:
|
|
159
|
+
print(f"SonarCloud: PR #{pr} has no unresolved issue (accepted ones pass)")
|
|
160
|
+
return 0
|
|
161
|
+
print(f"SonarCloud: PR #{pr} has {len(issues)} unresolved issue(s); fix or accept each:")
|
|
162
|
+
for issue in issues:
|
|
163
|
+
print(f" - {describe(issue, project)}")
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
sys.exit(main())
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# SonarCloud project identity. agentirc is analysed by SonarCloud's automatic
|
|
2
|
+
# analysis (the GitHub App), not by a CI scan step, so this file is not read by
|
|
3
|
+
# a scanner. scripts/sonar-pr-issues.py reads sonar.projectKey from it to find
|
|
4
|
+
# the PR's analysis and its unresolved issues.
|
|
5
|
+
sonar.projectKey=agentculture_agentirc
|
|
6
|
+
sonar.organization=agentculture
|
|
@@ -66,10 +66,10 @@ async def _boot_ircd(
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
async def _register(client: IRCTestClient, nick: str) -> None:
|
|
69
|
-
"""Drive NICK/USER and drain
|
|
69
|
+
"""Drive NICK/USER and drain the welcome burst through end-of-MOTD (bounded, fast)."""
|
|
70
70
|
await client.send(f"NICK {nick}")
|
|
71
71
|
await client.send(f"USER {nick} 0 * :{nick}")
|
|
72
|
-
await client.recv_until("
|
|
72
|
+
await client.recv_until(" 376 ")
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
async def _join(client: IRCTestClient, channel: str) -> None:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""CULTURE.DEV greeting: welcome/yourhost wording and MOTD numerics."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def _register(client, nick="testserv-ori"):
|
|
7
|
+
await client.send(f"NICK {nick}")
|
|
8
|
+
await client.send("USER ori 0 * :Ori")
|
|
9
|
+
return await client.recv_all(timeout=1.0)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _by_code(lines):
|
|
13
|
+
out = {}
|
|
14
|
+
for line in lines:
|
|
15
|
+
parts = line.split(" ", 3)
|
|
16
|
+
if len(parts) >= 2:
|
|
17
|
+
out.setdefault(parts[1], []).append(line)
|
|
18
|
+
return out
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.asyncio
|
|
22
|
+
async def test_welcome_and_yourhost_name_culture_dev(server, make_client):
|
|
23
|
+
codes = _by_code(await _register(await make_client()))
|
|
24
|
+
welcome = codes["001"][0]
|
|
25
|
+
assert "Welcome to CULTURE.DEV on testserv " in welcome
|
|
26
|
+
assert welcome.rstrip().endswith("testserv-ori!ori@127.0.0.1")
|
|
27
|
+
assert "™" not in welcome
|
|
28
|
+
yourhost = codes["002"][0]
|
|
29
|
+
assert "Your host is testserv, running CULTURE.DEV" in yourhost
|
|
30
|
+
assert "™" not in yourhost
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_myinfo_version_token_unchanged(server, make_client):
|
|
35
|
+
codes = _by_code(await _register(await make_client()))
|
|
36
|
+
assert codes["004"][0].split()[4] == "culture"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_motd_sent_on_registration(server, make_client):
|
|
41
|
+
lines = await _register(await make_client())
|
|
42
|
+
codes = [line.split()[1] for line in lines]
|
|
43
|
+
assert "375" in codes and "376" in codes
|
|
44
|
+
assert codes.index("375") < codes.index("372") < codes.index("376")
|
|
45
|
+
assert codes.index("004") < codes.index("375")
|
|
46
|
+
body = "\n".join(_by_code(lines)["372"])
|
|
47
|
+
assert "CULTURE.DEV" in body
|
|
48
|
+
assert "https://culture.dev" in body
|
|
49
|
+
assert "™" not in "\n".join(lines)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_motd_command(server, make_client):
|
|
54
|
+
client = await make_client()
|
|
55
|
+
await _register(client)
|
|
56
|
+
await client.send("MOTD")
|
|
57
|
+
lines = await client.recv_all(timeout=1.0)
|
|
58
|
+
codes = [line.split()[1] for line in lines]
|
|
59
|
+
assert codes[0] == "375" and codes[-1] == "376"
|
|
60
|
+
assert "372" in codes
|
|
61
|
+
assert any("CULTURE.DEV" in line for line in lines)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_motd_before_registration_not_served(server, make_client):
|
|
66
|
+
client = await make_client()
|
|
67
|
+
await client.send("MOTD")
|
|
68
|
+
lines = await client.recv_all(timeout=0.5)
|
|
69
|
+
assert not any(" 375 " in line or " 376 " in line for line in lines)
|
|
@@ -84,10 +84,10 @@ async def _connect(ircd: IRCd) -> IRCTestClient:
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
async def _register(client: IRCTestClient, nick: str) -> None:
|
|
87
|
-
"""Drive NICK/USER and drain
|
|
87
|
+
"""Drive NICK/USER and drain the welcome burst through end-of-MOTD (bounded, fast)."""
|
|
88
88
|
await client.send(f"NICK {nick}")
|
|
89
89
|
await client.send(f"USER {nick} 0 * :{nick}")
|
|
90
|
-
await client.recv_until("
|
|
90
|
+
await client.recv_until(" 376 ")
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def _presence_line(payload: dict) -> str:
|