agentirc-cli 6.2.1__tar.gz → 6.2.3__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-6.2.3/.claude/agents/doc-test-alignment.md +154 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.claude/skills/pr-review/SKILL.md +9 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/CHANGELOG.md +14 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/CLAUDE.md +10 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/PKG-INFO +1 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/app.py +33 -9
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/client.py +77 -35
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/pyproject.toml +1 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_client.py +99 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/uv.lock +1 -1
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.flake8 +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.github/workflows/docs-check.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.gitignore +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.pr_agent.toml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/.pylintrc +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/Gemfile +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/Gemfile.lock +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/LICENSE +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/README.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/SECURITY.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_config.agentirc.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_config.base.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_config.culture.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_data/sites.yml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_includes/head_custom.html +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_plugins/site_filter.rb +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_sass/color_schemes/dark-terminal.scss +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/IMG_3183.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/apple-touch-icon.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/favicon-16x16.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/favicon-32x32.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/favicon.ico +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/og-agentirc.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/assets/images/og-culture.png +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/__main__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/CLAUDE.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/__main__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/channel.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/docs/agentirc-architecture.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/docs/agentirc-features.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/docs/agentirc-skill.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/docs/agentirc.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/history_store.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/ircd.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/remote_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/room_store.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/rooms_util.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/server_link.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skill.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skills/history.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skills/icon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/skills/threads.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/agentirc/thread_store.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/aio.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/bot.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/agent.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/bot.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/channel.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/mesh.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/constants.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/display.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/formatting.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/mesh.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/shared/process.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/cli/skills.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/culture.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/culture.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/culture.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/culture.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/commands.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/status.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/credentials.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/formatting.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/learn_prompt.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/mesh_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/observer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/collector.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/model.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/overview/web/style.css +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/persistence.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/pidfile.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/commands.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/message.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/protocol/replies.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/README.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/agentirc/architecture-overview.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/agentirc/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/agentirc/why-agentirc.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/agent-lifecycle.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/choose-a-harness.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/mental-model.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/operate.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/patterns.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/quickstart.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/reflective-development.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/vision-patterns-index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/vision.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/culture/why-culture.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/architecture/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/architecture/layers.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/architecture/threads.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/cli/commands.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/cli/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/harnesses/acp.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/harnesses/claude.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/harnesses/codex.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/harnesses/copilot.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/harnesses/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/server/architecture.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/server/config.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/server/deployment.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/server/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/reference/server/security.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/federation.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/harnesses.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/humans-and-agents.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/persistence.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/concepts/rooms.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/demos/magic-demo.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/guides/first-session.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/guides/index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/guides/join-as-human.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/guides/local-setup.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/guides/multi-machine.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/shared/use-cases-index.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/favicon.ico +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/culture.yaml +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/robots.txt +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/sonar-project.properties +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/__init__.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/conftest.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_archive.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_bot.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_bot_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_channel.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_channel_cli.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_connection.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_commands.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_connection.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_fixes_224_227.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_icons.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_integration.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_console_status.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_credentials.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_culture_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_daemon.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_discovery.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_display.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_federation.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_history.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_http_listener.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_ipc.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_learn_prompt.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_manifest_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mention_alias.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mention_target_cleanup.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mention_warning.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mentions.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_mesh_readiness.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_message.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_messaging.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_migrate_cli.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_modes.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_overview_model.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_overview_web.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_persistence.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_pidfile.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_register_cli.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_rooms.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_skill_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_skill_docs.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_skills.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_socket_server.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_supervisor.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_template_engine.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_threads.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-6.2.1 → agentirc_cli-6.2.3}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doc-test-alignment
|
|
3
|
+
description: Audits a staged/branch diff for new public API surface (exceptions, classes, public functions, CLI commands, IRC protocol verbs, backend config fields) and reports whether `docs/` and `protocol/extensions/` mention them. Use at the end of a plan, before the first push, or when the user says "doc audit", "doc-test alignment", "check docs coverage".
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: cyan
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Doc-Test Alignment Auditor
|
|
10
|
+
|
|
11
|
+
You audit a code change for **doc drift**: new public API surface that
|
|
12
|
+
`docs/` doesn't mention. Run at the end of a plan (after tests pass, before
|
|
13
|
+
push). Your job is to surface omissions, not to write docs.
|
|
14
|
+
|
|
15
|
+
## Step 1 — Determine the diff
|
|
16
|
+
|
|
17
|
+
If the caller passed a specific ref or file list, use that. Otherwise, audit
|
|
18
|
+
the current branch against `main`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git diff --name-only main...HEAD
|
|
22
|
+
git diff main...HEAD -- '*.py' 'culture/cli/*' 'culture/agentirc/*'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If the branch has no commits ahead of `main`, fall back to staged changes:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git diff --cached --name-only
|
|
29
|
+
git diff --cached -- '*.py'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If neither yields a diff, report "no changes to audit" and stop.
|
|
33
|
+
|
|
34
|
+
## Step 2 — Extract new public API surface
|
|
35
|
+
|
|
36
|
+
Run the patterns via the **Grep tool** (ripgrep / Rust regex engine — the
|
|
37
|
+
Claude Code default). Patterns below are written in the Rust regex flavor
|
|
38
|
+
that ripgrep accepts without any extra flags: `\s`, `(?:...)`, and character
|
|
39
|
+
classes all work as written. Do **not** pipe these into POSIX `grep` / BRE
|
|
40
|
+
— they will silently fail to match.
|
|
41
|
+
|
|
42
|
+
Save the diff first, then search it:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git diff main...HEAD -- '*.py' 'culture/cli/*' 'culture/agentirc/*' > /tmp/branch-diff.patch
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then feed `/tmp/branch-diff.patch` to the Grep tool with each pattern below.
|
|
49
|
+
|
|
50
|
+
| Surface type | Ripgrep pattern |
|
|
51
|
+
|--------------|-----------------|
|
|
52
|
+
| New exception class | `^\+class [A-Z][A-Za-z0-9_]*(\(.*(?:Error\|Exception).*\))?:` |
|
|
53
|
+
| New public class | `^\+class [A-Z][A-Za-z0-9_]*` (exclude leading `_`) |
|
|
54
|
+
| New public function | `^\+(async )?def [a-z][a-z0-9_]*\(` (exclude leading `_`) |
|
|
55
|
+
| New CLI command | `^\+.*add_parser\(['"]` in `culture/cli/` |
|
|
56
|
+
| New IRC verb | `^\+.*(_msg_handlers\[['"]\|commands\s*=).*['"][A-Z0-9]+['"]` |
|
|
57
|
+
| New config field | `^\+.*@dataclass` or `^\+\s+[a-z_]+:\s+[A-Z].*=` in config modules |
|
|
58
|
+
|
|
59
|
+
Ignore private symbols (leading `_`), test helpers (inside `tests/`), and
|
|
60
|
+
internal-only classes (docstring contains "internal" or "private").
|
|
61
|
+
|
|
62
|
+
List every match with:
|
|
63
|
+
|
|
64
|
+
- symbol name
|
|
65
|
+
- file path + starting line
|
|
66
|
+
- kind (exception / class / function / CLI command / IRC verb / config field)
|
|
67
|
+
|
|
68
|
+
## Step 3 — Check doc coverage
|
|
69
|
+
|
|
70
|
+
For each extracted symbol, grep the documentation tree for a mention:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
grep -r -l '<symbol>' docs/ protocol/extensions/ 2>/dev/null
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Match rules:
|
|
77
|
+
|
|
78
|
+
- **Exception**: must be referenced in at least one `docs/**/*.md` page. If
|
|
79
|
+
it's a new public exception raised across module boundaries, that's a
|
|
80
|
+
stronger obligation — flag loudly.
|
|
81
|
+
- **CLI command**: must appear in `docs/` (user-facing command reference)
|
|
82
|
+
AND in the command's own module docstring.
|
|
83
|
+
- **IRC verb / protocol extension**: MUST have a page under
|
|
84
|
+
`protocol/extensions/` (per the repository-root `CLAUDE.md`: "Extensions
|
|
85
|
+
use new verbs (never redefine existing commands), documented in
|
|
86
|
+
`protocol/extensions/`").
|
|
87
|
+
- **Config field**: must appear in the relevant backend's `culture.yaml`
|
|
88
|
+
reference under `packages/agent-harness/culture.yaml` or
|
|
89
|
+
`docs/configuration.md`.
|
|
90
|
+
- **Public function/class**: soft obligation — flag if the symbol is
|
|
91
|
+
imported from outside its module, skip if it's used only within its
|
|
92
|
+
package.
|
|
93
|
+
|
|
94
|
+
## Step 4 — Check test coverage (light)
|
|
95
|
+
|
|
96
|
+
For each public surface symbol, grep `tests/` for at least one reference:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
grep -r -l '<symbol>' tests/ 2>/dev/null
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If zero test files reference the symbol, flag it. This is a weak signal —
|
|
103
|
+
the caller may have tested behavior without naming the symbol directly.
|
|
104
|
+
Report as "likely untested" rather than "untested".
|
|
105
|
+
|
|
106
|
+
## Step 5 — All-backends check (culture-specific)
|
|
107
|
+
|
|
108
|
+
If the diff touches `culture/clients/<backend>/` OR `packages/agent-harness/`,
|
|
109
|
+
list the other backends (`claude`, `codex`, `copilot`, `acp`) and report
|
|
110
|
+
whether the same change appears in each. This enforces the project's
|
|
111
|
+
"all-backends rule" (per the repository-root `CLAUDE.md`). A change in
|
|
112
|
+
only one backend is a bug.
|
|
113
|
+
|
|
114
|
+
## Step 6 — Report
|
|
115
|
+
|
|
116
|
+
Output a concise table. Factual only: what symbols, what's missing,
|
|
117
|
+
what's drifted. Do **not** write prose narration and do **not** suggest
|
|
118
|
+
what the docs should say — leave doc wording to the caller. A brief
|
|
119
|
+
counts-only summary line at the end is fine; skip it if everything is
|
|
120
|
+
clean.
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
## Doc-Test Alignment — <branch> vs main
|
|
124
|
+
|
|
125
|
+
### New public API surface
|
|
126
|
+
| Symbol | Kind | Location | Docs | Tests |
|
|
127
|
+
|--------|------|----------|------|-------|
|
|
128
|
+
| ConsoleConnectionLost | exception | culture/console/client.py:42 | MISSING | test_console_client.py |
|
|
129
|
+
| _handle_reconnect | private | culture/console/app.py:412 | (skip — private) | — |
|
|
130
|
+
|
|
131
|
+
### Protocol extensions
|
|
132
|
+
(none in this diff)
|
|
133
|
+
|
|
134
|
+
### All-backends drift
|
|
135
|
+
Change touches `packages/agent-harness/irc_transport.py`:
|
|
136
|
+
- claude: ✓ updated
|
|
137
|
+
- codex: ✓ updated
|
|
138
|
+
- copilot: ✗ NOT updated (divergent)
|
|
139
|
+
- acp: ✗ NOT updated (divergent)
|
|
140
|
+
|
|
141
|
+
### Summary
|
|
142
|
+
Doc gaps: 1 · Likely untested: 0 · Backend drift: 2
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If nothing is missing, output one line: `Doc-test alignment clean — no gaps detected.`
|
|
146
|
+
|
|
147
|
+
## What NOT to do
|
|
148
|
+
|
|
149
|
+
- Do not write documentation. Report gaps only.
|
|
150
|
+
- Do not modify code or tests.
|
|
151
|
+
- Do not fail loudly on borderline cases (internal-only helpers, test
|
|
152
|
+
utilities) — the caller is smarter than the heuristics; your value is in
|
|
153
|
+
catching the obvious omissions.
|
|
154
|
+
- Do not re-run the test suite — `/run-tests` is the tool for that.
|
|
@@ -171,7 +171,15 @@ bash ~/.claude/skills/pr-review/scripts/pr-reply.sh --resolve <PR_NUMBER> <COMME
|
|
|
171
171
|
- Always use `--resolve` to resolve the thread after replying
|
|
172
172
|
- Every comment must get a reply — no silent fixes
|
|
173
173
|
|
|
174
|
-
## Step 9 —
|
|
174
|
+
## Step 9 — Check SonarCloud before declaring ready
|
|
175
|
+
|
|
176
|
+
After CI is green and all inline threads are resolved, query SonarCloud
|
|
177
|
+
for the branch via the `/sonarclaude` skill. SonarCloud findings do not
|
|
178
|
+
always arrive as inline PR comments — a fully-resolved thread list plus an
|
|
179
|
+
all-green `gh pr checks` is **not** sufficient evidence that the PR is
|
|
180
|
+
clean. If `/sonarclaude` surfaces new findings, loop back to Step 7.
|
|
181
|
+
|
|
182
|
+
## Step 10 — Wait for merge
|
|
175
183
|
|
|
176
184
|
**Never merge the PR yourself.** The PR is merged manually on the GitHub site.
|
|
177
185
|
|
|
@@ -4,6 +4,20 @@ 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
|
+
## [6.2.3] - 2026-04-15
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- docs: post-#231 retrospective — CLAUDE.md guidance for pre-branch checklist, format-before-commit, pre-push code review, SonarCloud pre-ready; new doc-test-alignment subagent; /pr-review skill step for SonarCloud query
|
|
13
|
+
|
|
14
|
+
## [6.2.2] - 2026-04-14
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- console: handle BrokenPipeError/ConnectionResetError in _send_raw; surface a red system notice in the chat panel instead of letting the asyncio task crash (#230)
|
|
20
|
+
|
|
7
21
|
## [6.2.1] - 2026-04-13
|
|
8
22
|
|
|
9
23
|
|
|
@@ -49,13 +49,18 @@ Each backend has its own `culture.yaml` in `culture/clients/<backend>/`.
|
|
|
49
49
|
|
|
50
50
|
When implementing features, write a corresponding markdown doc in `docs/` describing the feature — its purpose, usage, and any protocol details. Keep `docs/` as the living reference for the project.
|
|
51
51
|
|
|
52
|
+
Before the first push on a branch that adds public API surface (new exceptions, CLI commands, IRC verbs, backend config fields), invoke the `doc-test-alignment` subagent to surface doc gaps: `Agent(subagent_type="doc-test-alignment", ...)`. It reads the branch diff and reports missing `docs/` coverage, missing protocol extension pages, and all-backends drift — it does not write docs, only flags omissions.
|
|
53
|
+
|
|
52
54
|
## Git Workflow
|
|
53
55
|
|
|
56
|
+
- **Before branching, run `git status`.** If `CHANGELOG.md`, any `CLAUDE.md`, or other files carry pre-existing unstaged changes on `main`, decide up front whether to stash, commit separately, or hand-split. `/version-bump` inserts a new section at the top of `CHANGELOG.md` and will interleave awkwardly with an existing `[Unreleased]` block if you don't.
|
|
54
57
|
- Branch out for all changes
|
|
55
|
-
- **Bump the version before creating a PR** — use `/version-bump patch` (bug fix), `minor` (new feature), or `major` (breaking change). This updates `pyproject.toml
|
|
58
|
+
- **Bump the version before creating a PR** — use `/version-bump patch` (bug fix), `minor` (new feature), or `major` (breaking change). This updates `pyproject.toml` and `CHANGELOG.md` (and `uv.lock` when applicable) in one step. Forgetting will fail the version-check CI job.
|
|
59
|
+
- **Pre-push review for library/protocol code.** When the diff touches shared choke points (transport, `_send_raw`-style I/O, protocol parsers, anything in `packages/` or `culture/agentirc/`), invoke a code reviewer on the staged diff before the first push — typed exceptions and new error paths routinely create caller cleanup obligations that Qodo/human reviewers otherwise surface in the first review round. Use `Agent(subagent_type="superpowers:code-reviewer", ...)` or `/review-and-fix`.
|
|
56
60
|
- Push to GitHub for agentic code review
|
|
57
61
|
- Pull review comments, address feedback, push fixes
|
|
58
62
|
- Reply to comments after pushing, resolve threads
|
|
63
|
+
- **Before declaring the PR ready**, check SonarCloud for the branch via the `/sonarclaude` skill. SonarCloud findings do not always arrive as inline PR comments, so an all-green `gh pr checks` + all-resolved threads is not sufficient.
|
|
59
64
|
|
|
60
65
|
## Testing
|
|
61
66
|
|
|
@@ -64,6 +69,10 @@ When implementing features, write a corresponding markdown doc in `docs/` descri
|
|
|
64
69
|
- No mocks for the server — tests spin up real server instances on random ports with real TCP connections
|
|
65
70
|
- Validate each layer with real IRC clients (weechat/irssi)
|
|
66
71
|
|
|
72
|
+
## Format Before Commit
|
|
73
|
+
|
|
74
|
+
Pre-commit runs `black`, `isort`, `flake8`, `pylint`, `bandit`. `black`/`isort` failures reformat the file and reject the commit — you then have to `git add` the reformatted file and commit again. To avoid the re-commit loop, run `uv run black <files>` and `uv run isort <files>` on staged Python files **before** `git commit`.
|
|
75
|
+
|
|
67
76
|
## Nick Format
|
|
68
77
|
|
|
69
78
|
`<server>-<agent>` (e.g., `thor-claude`, `spark-ori`). Globally unique by construction.
|
|
@@ -13,7 +13,7 @@ from textual.containers import Horizontal
|
|
|
13
13
|
from textual.widgets import Footer, Header
|
|
14
14
|
|
|
15
15
|
from culture.aio import maybe_await
|
|
16
|
-
from culture.console.client import ConsoleIRCClient
|
|
16
|
+
from culture.console.client import ConsoleConnectionLost, ConsoleIRCClient
|
|
17
17
|
from culture.console.commands import CommandType, parse_command
|
|
18
18
|
from culture.console.status import query_all_agents
|
|
19
19
|
from culture.console.widgets.chat import ChatPanel
|
|
@@ -74,6 +74,10 @@ class ConsoleApp(App):
|
|
|
74
74
|
self._background_tasks: set[asyncio.Task] = set()
|
|
75
75
|
self._status_poll_task: asyncio.Task | None = None
|
|
76
76
|
|
|
77
|
+
# Once the connection drops, show the "connection lost" notice exactly
|
|
78
|
+
# once — subsequent failing commands/channel-switches stay quiet.
|
|
79
|
+
self._connection_lost_notified: bool = False
|
|
80
|
+
|
|
77
81
|
# Dispatch table for command execution
|
|
78
82
|
self._command_handlers: dict[CommandType, Any] = {
|
|
79
83
|
CommandType.CHAT: self._handle_chat,
|
|
@@ -205,13 +209,29 @@ class ConsoleApp(App):
|
|
|
205
209
|
async def _execute_command(self, cmd) -> None: # noqa: ANN001
|
|
206
210
|
"""Dispatch a ParsedCommand to the appropriate handler."""
|
|
207
211
|
handler = self._command_handlers.get(cmd.type)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
try:
|
|
213
|
+
if handler:
|
|
214
|
+
await maybe_await(handler(cmd))
|
|
215
|
+
elif cmd.type in (CommandType.START, CommandType.STOP, CommandType.RESTART):
|
|
216
|
+
self._handle_agent_management(cmd)
|
|
217
|
+
elif cmd.type == CommandType.UNKNOWN:
|
|
218
|
+
chat: ChatPanel = self.query_one(ChatPanel)
|
|
219
|
+
chat.add_message(time.time(), "", "system", f"[red]Unknown command: {cmd.text}[/]")
|
|
220
|
+
except ConsoleConnectionLost:
|
|
221
|
+
self._notify_connection_lost()
|
|
222
|
+
|
|
223
|
+
def _notify_connection_lost(self) -> None:
|
|
224
|
+
"""Post the 'connection lost' notice once per disconnect."""
|
|
225
|
+
if self._connection_lost_notified:
|
|
226
|
+
return
|
|
227
|
+
self._connection_lost_notified = True
|
|
228
|
+
chat: ChatPanel = self.query_one(ChatPanel)
|
|
229
|
+
chat.add_message(
|
|
230
|
+
time.time(),
|
|
231
|
+
"",
|
|
232
|
+
"system",
|
|
233
|
+
"[red]Connection to server lost. Restart the console to reconnect.[/]",
|
|
234
|
+
)
|
|
215
235
|
|
|
216
236
|
# ------------------------------------------------------------------
|
|
217
237
|
# Command handlers
|
|
@@ -689,7 +709,11 @@ class ConsoleApp(App):
|
|
|
689
709
|
pass
|
|
690
710
|
|
|
691
711
|
# Fetch recent history
|
|
692
|
-
|
|
712
|
+
try:
|
|
713
|
+
entries = await self._client.history(channel, limit=20)
|
|
714
|
+
except ConsoleConnectionLost:
|
|
715
|
+
self._notify_connection_lost()
|
|
716
|
+
return
|
|
693
717
|
# Stale check: if user switched away during fetch, discard results
|
|
694
718
|
if self._current_channel != channel:
|
|
695
719
|
return
|
|
@@ -24,6 +24,10 @@ QUERY_TIMEOUT = 10.0
|
|
|
24
24
|
REGISTER_TIMEOUT = 15.0
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class ConsoleConnectionLost(ConnectionError):
|
|
28
|
+
"""Raised by ConsoleIRCClient when the underlying socket is broken mid-send."""
|
|
29
|
+
|
|
30
|
+
|
|
27
31
|
@dataclass
|
|
28
32
|
class ChatMessage:
|
|
29
33
|
"""A buffered chat message from a channel or DM."""
|
|
@@ -100,35 +104,49 @@ class ConsoleIRCClient:
|
|
|
100
104
|
timeout=REGISTER_TIMEOUT,
|
|
101
105
|
)
|
|
102
106
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Wait for RPL_WELCOME (001) before proceeding
|
|
107
|
-
welcome_future: asyncio.Future[Message] = asyncio.get_running_loop().create_future()
|
|
108
|
-
self._pending["001"] = welcome_future
|
|
107
|
+
try:
|
|
108
|
+
await self._send_raw(f"NICK {self.nick}")
|
|
109
|
+
await self._send_raw(f"USER {self.nick} 0 * :{self.nick}")
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
# Wait for RPL_WELCOME (001) before proceeding
|
|
112
|
+
welcome_future: asyncio.Future[Message] = asyncio.get_running_loop().create_future()
|
|
113
|
+
self._pending["001"] = welcome_future
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
except asyncio.TimeoutError:
|
|
116
|
-
self._pending.clear()
|
|
117
|
-
if self._read_task:
|
|
118
|
-
self._read_task.cancel()
|
|
119
|
-
if self._writer:
|
|
120
|
-
self._writer.close()
|
|
121
|
-
self._writer = None
|
|
122
|
-
self._reader = None
|
|
123
|
-
raise ConnectionError("Timed out waiting for server welcome (001)")
|
|
115
|
+
# Start the read loop so the future can be resolved
|
|
116
|
+
self._read_task = asyncio.create_task(self._read_loop())
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
try:
|
|
119
|
+
await asyncio.wait_for(welcome_future, timeout=REGISTER_TIMEOUT)
|
|
120
|
+
except asyncio.TimeoutError as e:
|
|
121
|
+
raise ConnectionError("Timed out waiting for server welcome (001)") from e
|
|
122
|
+
|
|
123
|
+
# Set user mode
|
|
124
|
+
if self.mode:
|
|
125
|
+
await self._send_raw(f"MODE {self.nick} +{self.mode}")
|
|
126
|
+
|
|
127
|
+
# Send ICON if provided
|
|
128
|
+
if self.icon:
|
|
129
|
+
await self._send_raw(f"ICON {self.icon}")
|
|
130
|
+
except BaseException:
|
|
131
|
+
# Any failure after open_connection: tear down the half-open state.
|
|
132
|
+
await self._teardown_connection()
|
|
133
|
+
raise
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
async def _teardown_connection(self) -> None:
|
|
136
|
+
"""Close writer, cancel reader, clear pending futures. Idempotent."""
|
|
137
|
+
self._pending.clear()
|
|
138
|
+
if self._read_task:
|
|
139
|
+
self._read_task.cancel()
|
|
140
|
+
await asyncio.gather(self._read_task, return_exceptions=True)
|
|
141
|
+
self._read_task = None
|
|
142
|
+
if self._writer:
|
|
143
|
+
try:
|
|
144
|
+
self._writer.close()
|
|
145
|
+
await self._writer.wait_closed()
|
|
146
|
+
except OSError:
|
|
147
|
+
pass
|
|
148
|
+
self._writer = None
|
|
149
|
+
self._reader = None
|
|
132
150
|
|
|
133
151
|
async def disconnect(self) -> None:
|
|
134
152
|
"""Send QUIT and close the connection."""
|
|
@@ -186,18 +204,24 @@ class ConsoleIRCClient:
|
|
|
186
204
|
Returns a sorted list of channel names.
|
|
187
205
|
"""
|
|
188
206
|
key = "LIST"
|
|
207
|
+
pending_key = "323"
|
|
189
208
|
self._collect_buffers[key] = []
|
|
190
209
|
end_future: asyncio.Future[None] = asyncio.get_running_loop().create_future()
|
|
191
|
-
self._pending[
|
|
210
|
+
self._pending[pending_key] = end_future
|
|
192
211
|
|
|
193
|
-
|
|
212
|
+
try:
|
|
213
|
+
await self._send_raw("LIST")
|
|
214
|
+
except ConsoleConnectionLost:
|
|
215
|
+
self._pending.pop(pending_key, None)
|
|
216
|
+
self._collect_buffers.pop(key, None)
|
|
217
|
+
raise
|
|
194
218
|
|
|
195
219
|
try:
|
|
196
220
|
await asyncio.wait_for(end_future, timeout=QUERY_TIMEOUT)
|
|
197
221
|
except asyncio.TimeoutError:
|
|
198
222
|
pass
|
|
199
223
|
finally:
|
|
200
|
-
self._pending.pop(
|
|
224
|
+
self._pending.pop(pending_key, None)
|
|
201
225
|
|
|
202
226
|
channels = self._collect_buffers.pop(key, [])
|
|
203
227
|
return sorted(channels)
|
|
@@ -208,18 +232,24 @@ class ConsoleIRCClient:
|
|
|
208
232
|
Returns a list of dicts with nick, user, host, server, flags, realname.
|
|
209
233
|
"""
|
|
210
234
|
key = f"WHO {target}"
|
|
235
|
+
pending_key = f"315:{target}"
|
|
211
236
|
self._collect_buffers[key] = []
|
|
212
237
|
end_future: asyncio.Future[None] = asyncio.get_running_loop().create_future()
|
|
213
|
-
self._pending[
|
|
238
|
+
self._pending[pending_key] = end_future
|
|
214
239
|
|
|
215
|
-
|
|
240
|
+
try:
|
|
241
|
+
await self._send_raw(f"WHO {target}")
|
|
242
|
+
except ConsoleConnectionLost:
|
|
243
|
+
self._pending.pop(pending_key, None)
|
|
244
|
+
self._collect_buffers.pop(key, None)
|
|
245
|
+
raise
|
|
216
246
|
|
|
217
247
|
try:
|
|
218
248
|
await asyncio.wait_for(end_future, timeout=QUERY_TIMEOUT)
|
|
219
249
|
except asyncio.TimeoutError:
|
|
220
250
|
pass
|
|
221
251
|
finally:
|
|
222
|
-
self._pending.pop(
|
|
252
|
+
self._pending.pop(pending_key, None)
|
|
223
253
|
|
|
224
254
|
entries = self._collect_buffers.pop(key, [])
|
|
225
255
|
return entries
|
|
@@ -230,18 +260,24 @@ class ConsoleIRCClient:
|
|
|
230
260
|
Returns a list of dicts with channel, nick, timestamp, text.
|
|
231
261
|
"""
|
|
232
262
|
key = f"HISTORY {channel}"
|
|
263
|
+
pending_key = f"HISTORYEND:{channel}"
|
|
233
264
|
self._collect_buffers[key] = []
|
|
234
265
|
end_future: asyncio.Future[None] = asyncio.get_running_loop().create_future()
|
|
235
|
-
self._pending[
|
|
266
|
+
self._pending[pending_key] = end_future
|
|
236
267
|
|
|
237
|
-
|
|
268
|
+
try:
|
|
269
|
+
await self._send_raw(f"HISTORY RECENT {channel} {limit}")
|
|
270
|
+
except ConsoleConnectionLost:
|
|
271
|
+
self._pending.pop(pending_key, None)
|
|
272
|
+
self._collect_buffers.pop(key, None)
|
|
273
|
+
raise
|
|
238
274
|
|
|
239
275
|
try:
|
|
240
276
|
await asyncio.wait_for(end_future, timeout=QUERY_TIMEOUT)
|
|
241
277
|
except asyncio.TimeoutError:
|
|
242
278
|
pass
|
|
243
279
|
finally:
|
|
244
|
-
self._pending.pop(
|
|
280
|
+
self._pending.pop(pending_key, None)
|
|
245
281
|
|
|
246
282
|
entries = self._collect_buffers.pop(key, [])
|
|
247
283
|
return entries
|
|
@@ -252,9 +288,15 @@ class ConsoleIRCClient:
|
|
|
252
288
|
|
|
253
289
|
async def _send_raw(self, line: str) -> None:
|
|
254
290
|
"""Write a raw IRC line to the socket."""
|
|
255
|
-
if self._writer:
|
|
291
|
+
if not self._writer:
|
|
292
|
+
return
|
|
293
|
+
try:
|
|
256
294
|
self._writer.write(f"{line}\r\n".encode())
|
|
257
295
|
await self._writer.drain()
|
|
296
|
+
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError) as e:
|
|
297
|
+
self.connected = False
|
|
298
|
+
logger.warning("ConsoleIRCClient: send failed (%s)", e.__class__.__name__)
|
|
299
|
+
raise ConsoleConnectionLost(str(e)) from e
|
|
258
300
|
|
|
259
301
|
async def _read_loop(self) -> None:
|
|
260
302
|
"""Background task: read lines from socket and dispatch to _handle."""
|
|
@@ -10,7 +10,7 @@ import asyncio
|
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
|
-
from culture.console.client import ChatMessage, ConsoleIRCClient
|
|
13
|
+
from culture.console.client import ChatMessage, ConsoleConnectionLost, ConsoleIRCClient
|
|
14
14
|
|
|
15
15
|
# ---------------------------------------------------------------------------
|
|
16
16
|
# Helpers
|
|
@@ -296,3 +296,101 @@ async def test_history_returns_messages(server, make_client):
|
|
|
296
296
|
assert any(e.get("text") == "history test message" for e in entries)
|
|
297
297
|
|
|
298
298
|
await client.disconnect()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# ---------------------------------------------------------------------------
|
|
302
|
+
# Broken-pipe handling (issue #230)
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@pytest.mark.asyncio
|
|
307
|
+
async def test_send_raw_raises_console_connection_lost_when_socket_broken(server):
|
|
308
|
+
"""Writes to a broken socket surface as ConsoleConnectionLost, not BrokenPipeError.
|
|
309
|
+
|
|
310
|
+
Reproduces issue #230: iTerm / idle disconnects caused asyncio's
|
|
311
|
+
`StreamWriter.drain()` to raise `BrokenPipeError` which was never caught,
|
|
312
|
+
crashing the console command task. The fix wraps the write in
|
|
313
|
+
`_send_raw` and re-raises a typed `ConsoleConnectionLost`.
|
|
314
|
+
"""
|
|
315
|
+
nick = "testserv-pipetest"
|
|
316
|
+
client = make_console_client(server, nick=nick)
|
|
317
|
+
await client.connect()
|
|
318
|
+
assert client.connected is True
|
|
319
|
+
|
|
320
|
+
# Force-close the server side of this client's socket. The server
|
|
321
|
+
# retains the asyncio StreamWriter on its Client object; closing it
|
|
322
|
+
# sends FIN to the console client and the next drain() there fails.
|
|
323
|
+
server_client = server.clients[nick]
|
|
324
|
+
server_client.writer.close()
|
|
325
|
+
try:
|
|
326
|
+
await server_client.writer.wait_closed()
|
|
327
|
+
except OSError:
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
# Repeated writes are required — the first drain() often succeeds because
|
|
331
|
+
# data lands in the local kernel buffer before the RST is observed.
|
|
332
|
+
with pytest.raises(ConsoleConnectionLost):
|
|
333
|
+
for _ in range(20):
|
|
334
|
+
await client.send_privmsg("#nowhere", "x" * 512)
|
|
335
|
+
await asyncio.sleep(0.02)
|
|
336
|
+
|
|
337
|
+
assert client.connected is False
|
|
338
|
+
await client.disconnect()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@pytest.mark.asyncio
|
|
342
|
+
async def test_history_cleans_up_pending_buffers_on_connection_lost(server):
|
|
343
|
+
"""A failed history() send must not leak _pending / _collect_buffers entries."""
|
|
344
|
+
nick = "testserv-histleak"
|
|
345
|
+
client = make_console_client(server, nick=nick)
|
|
346
|
+
await client.connect()
|
|
347
|
+
|
|
348
|
+
# Break the socket from the server side.
|
|
349
|
+
server.clients[nick].writer.close()
|
|
350
|
+
try:
|
|
351
|
+
await server.clients[nick].writer.wait_closed()
|
|
352
|
+
except OSError:
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
# Drain writes until ConsoleConnectionLost is raised by history().
|
|
356
|
+
raised = False
|
|
357
|
+
for _ in range(20):
|
|
358
|
+
try:
|
|
359
|
+
await client.history("#ghost", limit=5)
|
|
360
|
+
except ConsoleConnectionLost:
|
|
361
|
+
raised = True
|
|
362
|
+
break
|
|
363
|
+
await asyncio.sleep(0.02)
|
|
364
|
+
|
|
365
|
+
assert raised, "history() should eventually raise ConsoleConnectionLost"
|
|
366
|
+
# No stale state left behind — would otherwise hang future queries / leak memory.
|
|
367
|
+
assert "HISTORYEND:#ghost" not in client._pending
|
|
368
|
+
assert "HISTORY #ghost" not in client._collect_buffers
|
|
369
|
+
await client.disconnect()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@pytest.mark.asyncio
|
|
373
|
+
async def test_connect_cleans_up_on_registration_failure(monkeypatch, server):
|
|
374
|
+
"""A ConsoleConnectionLost mid-registration must close the writer, not leak it."""
|
|
375
|
+
client = make_console_client(server, nick="testserv-leaktest")
|
|
376
|
+
|
|
377
|
+
# Simulate a server drop between open_connection and the first NICK write.
|
|
378
|
+
original_send_raw = client._send_raw
|
|
379
|
+
calls = {"n": 0}
|
|
380
|
+
|
|
381
|
+
async def failing_send_raw(line: str) -> None:
|
|
382
|
+
calls["n"] += 1
|
|
383
|
+
if calls["n"] == 1:
|
|
384
|
+
raise ConsoleConnectionLost("simulated drop during registration")
|
|
385
|
+
await original_send_raw(line)
|
|
386
|
+
|
|
387
|
+
monkeypatch.setattr(client, "_send_raw", failing_send_raw)
|
|
388
|
+
|
|
389
|
+
with pytest.raises(ConsoleConnectionLost):
|
|
390
|
+
await client.connect()
|
|
391
|
+
|
|
392
|
+
# After failure, no half-open socket or dangling state.
|
|
393
|
+
assert client._writer is None
|
|
394
|
+
assert client._reader is None
|
|
395
|
+
assert client._read_task is None
|
|
396
|
+
assert client._pending == {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|