yaic 0.1.0 → 0.2.0

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/ralph-qa.md +101 -0
  3. data/.claude/ralph/bin/dump-pid.sh +3 -0
  4. data/.claude/ralph/bin/kill-claude +6 -0
  5. data/.claude/ralph/bin/start-ralph +44 -0
  6. data/.claude/ralph/bin/stop-hook.sh +9 -0
  7. data/.claude/ralph/bin/stop-ralph +17 -0
  8. data/.claude/ralph/prompt.md +218 -0
  9. data/.claude/settings.json +26 -0
  10. data/.gitmodules +3 -0
  11. data/CLAUDE.md +65 -0
  12. data/README.md +106 -17
  13. data/Rakefile +8 -0
  14. data/devenv.nix +1 -0
  15. data/docs/agents/data-model.md +150 -0
  16. data/docs/agents/ralph/features/01-message-parsing.md.done +160 -0
  17. data/docs/agents/ralph/features/01-tcpsocket-refactor.md.done +109 -0
  18. data/docs/agents/ralph/features/02-connection-socket.md.done +138 -0
  19. data/docs/agents/ralph/features/02-simplified-client-api.md.done +306 -0
  20. data/docs/agents/ralph/features/03-registration.md.done +147 -0
  21. data/docs/agents/ralph/features/04-ping-pong.md.done +109 -0
  22. data/docs/agents/ralph/features/05-event-system.md.done +167 -0
  23. data/docs/agents/ralph/features/06-privmsg-notice.md.done +163 -0
  24. data/docs/agents/ralph/features/07-join-part.md.done +190 -0
  25. data/docs/agents/ralph/features/08-quit.md.done +118 -0
  26. data/docs/agents/ralph/features/09-nick-change.md.done +109 -0
  27. data/docs/agents/ralph/features/10-topic.md.done +145 -0
  28. data/docs/agents/ralph/features/11-kick.md.done +122 -0
  29. data/docs/agents/ralph/features/12-names.md.done +124 -0
  30. data/docs/agents/ralph/features/13-mode.md.done +174 -0
  31. data/docs/agents/ralph/features/14-who-whois.md.done +188 -0
  32. data/docs/agents/ralph/features/15-client-api.md.done +180 -0
  33. data/docs/agents/ralph/features/16-ssl-test-infrastructure.md.done +50 -0
  34. data/docs/agents/ralph/features/17-github-actions-ci.md.done +70 -0
  35. data/docs/agents/ralph/features/18-brakeman-security-scanning.md.done +67 -0
  36. data/docs/agents/ralph/features/19-fix-qa.md.done +73 -0
  37. data/docs/agents/ralph/features/20-test-optimization.md.done +70 -0
  38. data/docs/agents/ralph/features/21-test-parallelization.md.done +56 -0
  39. data/docs/agents/ralph/features/22-wait-until-pattern.md.done +90 -0
  40. data/docs/agents/ralph/features/23-ping-test-optimization.md.done +46 -0
  41. data/docs/agents/ralph/features/24-blocking-who-whois.md.done +159 -0
  42. data/docs/agents/ralph/features/25-verbose-mode.md.done +166 -0
  43. data/docs/agents/ralph/plans/test-optimization-plan.md +172 -0
  44. data/docs/agents/ralph/progress.md +731 -0
  45. data/docs/agents/todo.md +5 -0
  46. data/lib/yaic/channel.rb +22 -0
  47. data/lib/yaic/client.rb +821 -0
  48. data/lib/yaic/event.rb +35 -0
  49. data/lib/yaic/message.rb +119 -0
  50. data/lib/yaic/registration.rb +17 -0
  51. data/lib/yaic/socket.rb +120 -0
  52. data/lib/yaic/source.rb +39 -0
  53. data/lib/yaic/version.rb +1 -1
  54. data/lib/yaic/who_result.rb +17 -0
  55. data/lib/yaic/whois_result.rb +20 -0
  56. data/lib/yaic.rb +13 -1
  57. metadata +51 -1
@@ -0,0 +1,731 @@
1
+ # Progress Log
2
+
3
+ ## Testing Rules
4
+
5
+ - **No skipping tests** unless it's for a feature we explicitly want to implement later (e.g., SSL tests pending infrastructure)
6
+ - Tests should **flunk** (fail) if prerequisites are missing (e.g., IRC server not running)
7
+ - A failing test means: either ask the user for help (start a server) or fix the code
8
+ - **No sleep in tests** - make tests fast and reliable
9
+ - Use unique generated nicknames for every test to avoid collisions
10
+
11
+ ## Current State
12
+
13
+ Features 01-17 complete. CI now runs on push to main and PRs.
14
+
15
+ ## Feature Order
16
+
17
+ Features should be implemented in this order (dependencies noted):
18
+
19
+ 1. ~~`01-message-parsing.md`~~ ✅ - No deps, foundation for everything
20
+ 2. ~~`02-connection-socket.md`~~ ✅ - Depends on 01
21
+ 3. ~~`03-registration.md`~~ ✅ - Depends on 01, 02
22
+ 4. ~~`04-ping-pong.md`~~ ✅ - Depends on 01, 02, 03
23
+ 5. ~~`05-event-system.md`~~ ✅ - Depends on 01
24
+ 6. ~~`06-privmsg-notice.md`~~ ✅ - Depends on 01-05
25
+ 7. ~~`07-join-part.md`~~ ✅ - Depends on 01-05
26
+ 8. ~~`08-quit.md`~~ ✅ - Depends on 01, 02, 05
27
+ 9. ~~`09-nick-change.md`~~ ✅ - Depends on 01, 02, 03, 05
28
+ 10. ~~`10-topic.md`~~ ✅ - Depends on 07
29
+ 11. ~~`11-kick.md`~~ ✅ - Depends on 07
30
+ 12. ~~`12-names.md`~~ ✅ - Depends on 07
31
+ 13. ~~`13-mode.md`~~ ✅ - Depends on 07
32
+ 14. ~~`14-who-whois.md`~~ ✅ - Depends on 01, 05
33
+ 15. ~~`15-client-api.md`~~ ✅ - Depends on ALL (final integration)
34
+ 16. ~~`16-ssl-test-infrastructure.md`~~ ✅ - Depends on 02 (SSL testing)
35
+
36
+ ---
37
+
38
+ ## Session History
39
+
40
+ ### Session 2025-11-28
41
+
42
+ **Feature**: 01-message-parsing
43
+ **Status**: Completed
44
+
45
+ **What was done**:
46
+ - Implemented `Yaic::Source` class with parsing for all source formats (server, nick!user@host, nick!user, nick@host, nick)
47
+ - Implemented `Yaic::Message` class with `parse` class method and `to_s` serialization
48
+ - Used StringScanner for efficient parsing
49
+ - Handled edge cases: LF-only line endings, empty lines, multiple spaces, UTF-8/Latin-1 encoding
50
+ - Created unit tests for both classes (21 tests, 40 assertions)
51
+ - All tests pass, linter clean, QA passed
52
+
53
+ **Notes for next session**:
54
+ - Message and Source classes are available via `require "yaic"`
55
+ - Public interface: `Yaic::Message.parse(str)`, `msg.to_s`, attribute readers (tags, source, command, params, raw)
56
+ - Public interface: `Yaic::Source.parse(str)`, attribute readers (nick, user, host, raw)
57
+
58
+ ### Session 2025-11-28 (2)
59
+
60
+ **Feature**: 02-connection-socket
61
+ **Status**: Completed
62
+
63
+ **What was done**:
64
+ - Implemented `Yaic::Socket` class with TCP/SSL connection support
65
+ - Non-blocking connect with configurable timeout
66
+ - Read buffering with message extraction (handles \r\n and \n line endings)
67
+ - Write with automatic \r\n appending
68
+ - IPv4/IPv6 address resolution (prefers IPv4)
69
+ - Created `bin/start-irc-server` and `bin/stop-irc-server` for Docker container management
70
+ - Created `16-ssl-test-infrastructure.md` feature spec for future SSL testing
71
+ - Unit tests for buffer handling (6 tests)
72
+ - Integration tests for TCP connections (7 tests, 2 skipped for SSL)
73
+ - All tests pass, linter clean, QA passed
74
+
75
+ **Notes for next session**:
76
+ - Socket class available via `require "yaic"`
77
+ - Public interface: `Yaic::Socket.new(host, port, ssl:, verify_mode:, connect_timeout:)`, `connect`, `disconnect`, `read`, `write`, `state`
78
+ - SSL tests skip with message referencing `16-ssl-test-infrastructure.md`
79
+ - Integration tests require IRC server: run `bin/start-irc-server` first
80
+
81
+ ### Session 2025-11-28 (3)
82
+
83
+ **Feature**: 03-registration
84
+ **Status**: Completed
85
+
86
+ **What was done**:
87
+ - Implemented `Yaic::Registration` module with message factory methods (pass_message, nick_message, user_message)
88
+ - Implemented `Yaic::Client` class with connection state machine (:disconnected → :connecting → :registering → :connected)
89
+ - Handles NICK/USER registration sequence, optional PASS command
90
+ - Handles RPL_WELCOME (001) to confirm registration
91
+ - Handles RPL_ISUPPORT (005) to parse server capabilities
92
+ - Handles ERR_NICKNAMEINUSE (433) with automatic nick retry (appends underscores)
93
+ - Created custom InspIRCd config (`test/fixtures/inspircd.conf`) to disable connection throttling
94
+ - Updated `bin/start-irc-server` to mount custom config
95
+ - Added RUBOCOP_CACHE_ROOT to devenv.nix
96
+ - Unit tests for message formatting (4 tests) and state machine (6 tests)
97
+ - Integration tests for registration flows (4 tests)
98
+ - All tests pass, linter clean, QA passed
99
+
100
+ **Deferred items**:
101
+ - Event emission for nick collision: Deferred to 05-event-system (event system not yet implemented)
102
+ - Password integration test: Deferred (test server not configured with password requirement)
103
+
104
+ **Notes for next session**:
105
+ - Registration module available via `require "yaic"`
106
+ - Public interface: `Yaic::Registration.pass_message(pw)`, `.nick_message(nick)`, `.user_message(user, realname)`
107
+ - Client class available via `require "yaic"`
108
+ - Public interface: `Yaic::Client.new(host:, port:, nick:, user:, realname:, password:, ssl:)`, `connect`, `disconnect`, `state`, `nick`, `isupport`
109
+ - Integration tests require IRC server: run `bin/start-irc-server` first
110
+
111
+ ### Session 2025-11-28 (4)
112
+
113
+ **Feature**: 04-ping-pong
114
+ **Status**: Completed
115
+
116
+ **What was done**:
117
+ - Added PING handling to `Yaic::Client` class in `handle_message`
118
+ - Automatically responds to PING with PONG (token mirrored exactly)
119
+ - Works during registration and when connected
120
+ - Handles PING with or without colon prefix
121
+ - Added `STALE_TIMEOUT = 180` constant for connection timeout detection
122
+ - Added `last_received_at` attribute (updated on every message received)
123
+ - Added `connection_stale?` method to check if connection may be dead
124
+ - Reduced `pingfreq` in test server config from 120s to 5s for faster testing
125
+ - Unit tests for PING/PONG handling and connection staleness (8 tests)
126
+ - Integration tests including Client automatically responding to server-initiated PINGs (4 tests)
127
+ - All tests pass, linter clean, QA passed
128
+
129
+ **Notes for next session**:
130
+ - Client class now handles PING automatically in `handle_message`
131
+ - Public interface additions: `last_received_at`, `connection_stale?`
132
+ - PONG response uses Message class so spaces in token get proper `:` trailing prefix
133
+ - Integration tests now use 5-second ping frequency for fast testing
134
+
135
+ ### Session 2025-11-28 (5)
136
+
137
+ **Feature**: 05-event-system
138
+ **Status**: Completed
139
+
140
+ **What was done**:
141
+ - Created `Yaic::Event` class with type, message, and dynamic attribute access via method_missing
142
+ - Added `on(event_type, &block)` and `off(event_type)` methods to Client for handler registration
143
+ - Implemented event dispatch with error handling (exceptions in handlers don't stop other handlers)
144
+ - Added `:raw` event emitted for every message received
145
+ - Added typed events: `:connect`, `:message`, `:notice`, `:join`, `:part`, `:quit`, `:kick`, `:nick`, `:topic`, `:mode`, `:error`
146
+ - Event payloads match spec (source, target, text, channel, user, etc.)
147
+ - Unit tests for Event class (7 tests)
148
+ - Unit tests for handler registration and dispatch (17 new tests in client_test.rb)
149
+ - Integration tests for connect, join, message, and raw events (4 tests)
150
+ - All tests pass, linter clean, QA passed
151
+
152
+ **Notes for next session**:
153
+ - Event class available via `require "yaic"`
154
+ - Public interface: `client.on(:event) { |e| ... }`, `client.off(:event)`
155
+ - Event object: `event.type`, `event.message`, `event.<attribute>` (dynamic access)
156
+ - `:disconnect` event not implemented (no event loop yet to detect disconnection)
157
+
158
+ ### Session 2025-11-28 (6)
159
+
160
+ **Feature**: 06-privmsg-notice
161
+ **Status**: Completed
162
+
163
+ **What was done**:
164
+ - Added `privmsg(target, text)` method to Client for sending private messages
165
+ - Added `msg(target, text)` as alias for `privmsg`
166
+ - Added `notice(target, text)` method to Client for sending notices
167
+ - Modified Message#to_s to always use trailing prefix (`:`) for last param when multiple params exist
168
+ - Fixed pre-existing failing test in socket_test.rb (test assumed server sends message on connect)
169
+ - Unit tests for message formatting (PRIVMSG/NOTICE with special chars)
170
+ - Unit tests for event parsing (PRIVMSG/NOTICE events)
171
+ - Integration tests for sending to channels and users (4 tests)
172
+ - Integration tests for receiving messages and notices (4 tests)
173
+ - Integration tests for error handling (401 no such nick, 403/404 no such channel)
174
+ - All tests pass, linter clean, QA passed
175
+
176
+ **Notes for next session**:
177
+ - Public interface additions: `client.privmsg(target, text)`, `client.msg(target, text)`, `client.notice(target, text)`
178
+ - Message text always uses trailing prefix for proper formatting
179
+ - Error events emitted for 401/403/404 errors
180
+
181
+ ### Session 2025-11-28 (7)
182
+
183
+ **Feature**: 07-join-part
184
+ **Status**: Completed
185
+
186
+ **What was done**:
187
+ - Created `Yaic::Channel` class for tracking joined channels
188
+ - Added `client.channels` hash to track which channels client is in
189
+ - Added `client.join(channel, key = nil)` method for joining channels
190
+ - Added `client.part(channel, reason = nil)` method for leaving channels
191
+ - Channel tracking: self-join adds channel, self-part removes channel
192
+ - Other users' JOIN/PART does not affect channel tracking (events still emitted)
193
+ - Fixed `Message#to_s` to only use trailing prefix when actually needed (spaces, empty, starts with colon)
194
+ - Updated server config to remove `+t` default mode so tests can set topics
195
+ - Unit tests for JOIN/PART formatting and event parsing (10 new tests)
196
+ - Integration tests for join, part, topics, multiple channels, events (11 tests)
197
+ - All tests pass (127 runs, 251 assertions, 2 SSL skips), linter clean, QA passed
198
+
199
+ **Notes for next session**:
200
+ - Channel class available via `require "yaic"`
201
+ - Public interface: `client.join(channel, key)`, `client.part(channel, reason)`, `client.channels`
202
+ - Channel object: `channel.name`, `channel.topic`, `channel.users`, `channel.modes` (users/modes not yet populated)
203
+ - Server config now uses `defaultmodes="n"` instead of `"nt"` to allow topic setting without +o
204
+
205
+ ### Session 2025-11-28 (8)
206
+
207
+ **Feature**: 08-quit
208
+ **Status**: Completed
209
+
210
+ **What was done**:
211
+ - Added `client.quit(reason = nil)` method for gracefully disconnecting from server
212
+ - Sends `QUIT\r\n` (no reason) or `QUIT :reason\r\n` (with reason)
213
+ - Clears all tracked channels after quit
214
+ - Sets state to `:disconnected`
215
+ - Emits `:disconnect` event after quit
216
+ - Parses QUIT events from other users with `user` and `reason` attributes
217
+ - Unit tests for QUIT formatting, event parsing, state changes, channel cleanup (7 new tests)
218
+ - Integration tests for quit without/with reason, receiving other user quit, netsplit detection (4 tests)
219
+ - All tests pass (138 runs, 271 assertions, 2 SSL skips), linter clean, QA passed
220
+
221
+ **Notes for next session**:
222
+ - Public interface additions: `client.quit(reason = nil)`
223
+ - `:disconnect` event now implemented (emitted after quit)
224
+ - QUIT reason may be nil if server doesn't include one
225
+
226
+ ### Session 2025-11-28 (9)
227
+
228
+ **Feature**: 09-nick-change
229
+ **Status**: Completed
230
+
231
+ **What was done**:
232
+ - Added `client.nick(new_nick)` method for changing nickname (sends `NICK new_nick\r\n`)
233
+ - Modified `nick` to work as both getter (no arg) and setter (with arg) - removed from attr_reader
234
+ - Added `handle_nick(message)` to update internal nick when self changes nick
235
+ - Added channel user list updates when any user changes nick (iterates all channels)
236
+ - Fixed `handle_err_nicknameinuse` to only auto-retry during registration (not when connected)
237
+ - Unit tests for NICK formatting, event parsing, own nick tracking, channel user tracking (5 new tests)
238
+ - Integration tests for nick change, nick in use, invalid nick, other user changes nick (4 tests)
239
+ - All tests pass (148 runs, 293 assertions, 2 SSL skips), linter clean, QA passed
240
+
241
+ **Notes for next session**:
242
+ - Public interface: `client.nick` (getter), `client.nick("new")` (setter)
243
+ - `:nick` event emitted with `old_nick`, `new_nick` attributes
244
+ - Channel user tracking updates across all channels when nick changes
245
+ - 433 (nick in use) only auto-retries during registration state
246
+
247
+ ### Session 2025-11-28 (10)
248
+
249
+ **Feature**: 10-topic
250
+ **Status**: Completed
251
+
252
+ **What was done**:
253
+ - Added `client.topic(channel, new_topic = nil)` method for getting/setting topics
254
+ - Added `set_topic(topic, setter, time)` method to Channel class
255
+ - Added handlers for TOPIC command, 332 (RPL_TOPIC), 333 (RPL_TOPICWHOTIME)
256
+ - Topic changes update channel.topic, channel.topic_setter, channel.topic_time
257
+ - `:topic` event emitted with channel, topic, setter attributes
258
+ - Modified Message#to_s to always use trailing prefix for last param when 2+ params (better IRC compliance)
259
+ - Unit tests for TOPIC formatting, parsing, channel state tracking (8 new tests)
260
+ - Integration tests for get topic, get no topic, set topic, clear topic, receive topic change (5 tests)
261
+ - 1 integration test skipped (set topic without permission) due to InspIRCd 4 not auto-opping channel creators
262
+ - All tests pass (161 runs, 319 assertions, 3 skips), linter clean, QA passed
263
+
264
+ **Notes for next session**:
265
+ - Public interface: `client.topic(channel)` (get), `client.topic(channel, text)` (set), `client.topic(channel, "")` (clear)
266
+ - Channel object now has: `topic`, `topic_setter`, `topic_time` attributes
267
+ - Message#to_s now always uses trailing prefix for multi-param messages (proper IRC format)
268
+ - InspIRCd 4 limitation: users aren't auto-opped when creating channels, affecting +t mode tests
269
+
270
+ ### Session 2025-11-29 (11)
271
+
272
+ **Feature**: 11-kick
273
+ **Status**: Completed
274
+
275
+ **What was done**:
276
+ - Added `client.kick(channel, nick, reason = nil)` method for kicking users from channels
277
+ - Added `handle_kick(message)` to update channel state (remove kicked user from channel, remove channel when self is kicked)
278
+ - Added KICK to handle_message dispatch
279
+ - Updated InspIRCd config to add `samode` module for server operator channel mode control
280
+ - Added `oper` block to InspIRCd config for test authentication
281
+ - Unit tests for KICK formatting and event parsing (6 new tests)
282
+ - Integration tests for kick user, kick with reason, kick without permission, kick non-existent user, receive kick others, receive kick self (6 tests)
283
+ - All tests pass (172 runs, 345 assertions, 3 skips), linter clean, QA passed
284
+
285
+ **Notes for next session**:
286
+ - Public interface: `client.kick(channel, nick)`, `client.kick(channel, nick, reason)`
287
+ - `:kick` event emitted with `channel`, `user`, `by`, `reason` attributes
288
+ - State tracking: kicked user removed from channel.users, channel removed from client.channels when self is kicked
289
+ - InspIRCd test server now has oper credentials (testoper/testpass) and SAMODE for setting channel ops
290
+
291
+ ### Session 2025-11-29 (12)
292
+
293
+ **Feature**: 12-names
294
+ **Status**: Completed
295
+
296
+ **What was done**:
297
+ - Added `client.names(channel)` method for requesting channel user lists
298
+ - Added `handle_rpl_namreply(message)` to collect users from 353 replies
299
+ - Added `handle_rpl_endofnames(message)` to finalize and emit `:names` event
300
+ - Added `parse_user_with_prefix(user_entry)` to parse user mode prefixes
301
+ - Added `PREFIX_MODES` constant mapping @, +, %, ~, & to :op, :voice, :halfop, :owner, :admin
302
+ - Added `@pending_names` hash to accumulate users across multiple 353 messages
303
+ - Users populated into `channel.users` as `Hash[String, Set[Symbol]]`
304
+ - `:names` event emitted with `channel:` and `users:` attributes
305
+ - Unit tests for NAMES formatting, prefix parsing, multi-message collection (12 tests)
306
+ - Integration tests for get names, names with prefixes, names at join, multi-message names (4 tests)
307
+ - All tests pass (189 runs, 400 assertions, 3 skips), linter clean, QA passed
308
+
309
+ **Notes for next session**:
310
+ - Public interface: `client.names(channel)`
311
+ - `:names` event emitted with `channel:` (String) and `users:` (Hash[String, Set[Symbol]])
312
+ - `channel.users` is populated from NAMES responses
313
+ - Prefix parsing handles @op, +voice, %halfop, ~owner, &admin and multiple prefixes
314
+
315
+ ### Session 2025-11-28 (13)
316
+
317
+ **Feature**: 13-mode
318
+ **Status**: Completed
319
+
320
+ **What was done**:
321
+ - Added `client.mode(target, modes = nil, *args)` method for querying/setting modes
322
+ - Added `handle_mode(message)` to parse MODE messages and update channel state
323
+ - Added `apply_user_mode(channel, nick, mode_char, adding)` for user mode tracking
324
+ - Channel mode tracking: moderated, invite_only, key, limit, topic_protected, no_external, secret, private
325
+ - User mode tracking in channels: op, voice, halfop, admin, owner
326
+ - Correctly parses +/- mode prefixes and handles mode parameters
327
+ - Unit tests for MODE formatting, event parsing, channel/user state tracking (14 new tests)
328
+ - Integration tests for user modes (get own, set invisible, cannot set other's) and channel modes (get, set as op, give op, set key, without permission) (8 tests)
329
+ - All tests pass (209 runs, 429 assertions, 3 skips), linter clean, QA passed
330
+
331
+ **Notes for next session**:
332
+ - Public interface: `client.mode(target)` (query), `client.mode(target, modes)` (set), `client.mode(target, modes, *args)` (set with params)
333
+ - `:mode` event emitted with `target`, `modes`, `args` attributes
334
+ - `channel.modes` is a Hash[Symbol, Object] with keys like :moderated, :key, :limit
335
+ - User modes tracked in `channel.users[nick]` as Set of symbols (:op, :voice, etc.)
336
+
337
+ ### Session 2025-11-29 (14)
338
+
339
+ **Feature**: 14-who-whois
340
+ **Status**: Completed
341
+
342
+ **What was done**:
343
+ - Created `Yaic::WhoisResult` class for aggregating WHOIS data
344
+ - Added `client.who(mask)` method for WHO queries
345
+ - Added `client.whois(nick)` method for WHOIS queries
346
+ - Added handlers for WHO numeric: 352 (RPL_WHOREPLY)
347
+ - Added handlers for WHOIS numerics: 311 (RPL_WHOISUSER), 319 (RPL_WHOISCHANNELS), 312 (RPL_WHOISSERVER), 317 (RPL_WHOISIDLE), 330 (RPL_WHOISACCOUNT), 301 (RPL_AWAY), 318 (RPL_ENDOFWHOIS)
348
+ - WHOIS data collected in `@pending_whois` until ENDOFWHOIS, then emitted as single `:whois` event
349
+ - `:who` event emitted for each RPL_WHOREPLY with channel, user, host, server, nick, away, realname
350
+ - Correctly handles interleaved messages during WHOIS collection
351
+ - Unit tests for WHO/WHOIS formatting, parsing, and collection (16 tests)
352
+ - Integration tests for WHO channel, WHO nick, WHO non-existent, WHOIS user, WHOIS with channels, WHOIS non-existent, WHOIS away user (7 tests)
353
+ - All tests pass (232 runs, 463 assertions, 3 skips), linter clean, QA passed
354
+
355
+ **Notes for next session**:
356
+ - Public interface: `client.who(mask)`, `client.whois(nick)`
357
+ - `:who` event emitted for each matching user with: channel, user, host, server, nick, away (boolean), realname
358
+ - `:whois` event emitted after ENDOFWHOIS with `result:` (WhoisResult or nil if not found)
359
+ - WhoisResult attributes: nick, user, host, realname, channels (Array), server, idle, signon (Time), account, away
360
+
361
+ ### Session 2025-11-28 (15)
362
+
363
+ **Feature**: 15-client-api
364
+ **Status**: Completed
365
+
366
+ **What was done**:
367
+ - Added `connected?` method that returns true when state is :connected
368
+ - Added `server` getter as attr_reader
369
+ - Added parameter aliases in Client#initialize:
370
+ - `server:` (alias for `host:`)
371
+ - `nickname:` (alias for `nick:`)
372
+ - `username:` (alias for `user:`)
373
+ - Default handling: username and realname default to nickname if not provided
374
+ - Connection errors bubble up naturally from Socket (Errno::ECONNREFUSED, etc.)
375
+ - Unit tests for new Client API features (17 new tests)
376
+ - Integration tests for full client workflow (6 tests)
377
+ - All tests pass (254 runs, 559 assertions, 3 skips), linter clean, QA passed
378
+
379
+ **Notes for next session**:
380
+ - All planned features complete!
381
+ - Public API supports both naming conventions:
382
+ - Original: `host:`, `nick:`, `user:`
383
+ - New: `server:`, `nickname:`, `username:`
384
+ - `client.connected?` returns boolean state
385
+ - `client.server` returns configured server hostname
386
+
387
+ ---
388
+
389
+ ### Session 2025-11-29 (16)
390
+
391
+ **Feature**: 16-ssl-test-infrastructure
392
+ **Status**: Completed
393
+
394
+ **What was done**:
395
+ - Generated self-signed SSL certificates (cert.pem, key.pem) in `test/fixtures/ssl/`
396
+ - Updated `test/fixtures/inspircd.conf` to enable SSL on port 6697 using GnuTLS module
397
+ - Updated `bin/start-irc-server` to mount SSL certificates into the Docker container
398
+ - Replaced skip statements in SSL tests with actual test implementations:
399
+ - `test_connect_with_ssl` - verifies SSL connection with VERIFY_NONE
400
+ - `test_ssl_read_write` - verifies read/write operations over SSL
401
+ - `test_ssl_verify_peer_fails_self_signed` - verifies VERIFY_PEER fails with self-signed cert
402
+ - Added `require_ssl_server_available` helper method
403
+ - All tests pass (255 runs, 563 assertions, 1 skip for unrelated topic test)
404
+
405
+ **Notes for next session**:
406
+ - SSL tests now run automatically (no more skips)
407
+ - Uses GnuTLS module (not OpenSSL) as that's what the inspircd Docker image provides
408
+ - To run tests with SSL, restart server with `bin/stop-irc-server && bin/start-irc-server`
409
+
410
+ ### Session 2025-11-29 (17)
411
+
412
+ **Feature**: 17-github-actions-ci
413
+ **Status**: Completed
414
+
415
+ **What was done**:
416
+ - Updated `.github/workflows/main.yml`:
417
+ - Changed trigger from `master` to `main`
418
+ - Added separate `lint` job running `bundle exec standardrb` (Ruby 3.4)
419
+ - Added `test` job with matrix for Ruby 3.2 and 3.4
420
+ - Added IRC server (Docker container) for integration tests
421
+ - Runs full test suite with `bundle exec rake test`
422
+ - Updated `Rakefile`:
423
+ - Added `test_unit` rake task for unit tests only
424
+ - Added `test_integration` rake task for integration tests
425
+ - Added `.gitmodules` to fix broken submodule reference for `docs/agents/rfc/modern-irc`
426
+ - CI run 19780791916 passed (Lint ✓, Ruby 3.2 ✓, Ruby 3.4 ✓)
427
+
428
+ **Notes for next session**:
429
+ - CI runs full test suite including integration tests (IRC server started via Docker)
430
+ - Use `bundle exec rake test_unit` for fast unit tests locally
431
+ - Use `bundle exec rake test_integration` for integration tests (requires IRC server)
432
+
433
+ ### Session 2025-11-28 (18)
434
+
435
+ **Feature**: 18-brakeman-security-scanning
436
+ **Status**: Completed
437
+
438
+ **What was done**:
439
+ - Added brakeman gem to Gemfile
440
+ - Created `bin/brakeman` wrapper script (uses --force since this is a gem, not Rails)
441
+ - Added security job to `.github/workflows/main.yml` running brakeman
442
+ - Updated QA agent instructions to include brakeman in validation commands
443
+ - Brakeman passes with no warnings
444
+
445
+ **Notes for next session**:
446
+ - Run `bin/brakeman` to check for security issues
447
+ - CI now has lint, security, and test jobs
448
+ - QA agent now checks for security issues as part of review
449
+
450
+ ---
451
+
452
+ ## Suggested Next Feature
453
+
454
+ All 16 planned features are complete. The library now provides:
455
+ - IRC message parsing and serialization
456
+ - TCP/SSL connection management
457
+ - Registration (NICK, USER, PASS)
458
+ - PING/PONG handling
459
+ - Event system with handlers
460
+ - PRIVMSG/NOTICE
461
+ - JOIN/PART with channel tracking
462
+ - QUIT with disconnect events
463
+ - NICK changes with tracking
464
+ - TOPIC get/set
465
+ - KICK
466
+ - NAMES with user prefix parsing
467
+ - MODE for channels and users
468
+ - WHO/WHOIS queries
469
+ - Client API with convenience methods
470
+
471
+ ### Session 2025-11-29 (19)
472
+
473
+ **Feature**: 01-tcpsocket-refactor
474
+ **Status**: Completed
475
+
476
+ **What was done**:
477
+ - Refactored `lib/yaic/socket.rb` to use `TCPSocket.new` with `connect_timeout:` parameter
478
+ - Removed `resolve_address` method (no longer needed - TCPSocket handles DNS resolution)
479
+ - Removed nonblocking connect logic (`connect_nonblock`, `IO.select`, `IO::WaitWritable`)
480
+ - Preserved keepalive via `setsockopt`
481
+ - Added `test_dns_resolution_failure` integration test (verifies SocketError for bad hostnames)
482
+ - Added `test_keepalive_option_set` integration test (verifies SO_KEEPALIVE enabled)
483
+ - All tests pass (258 runs, 566 assertions, 0 failures)
484
+
485
+ **Notes for next session**:
486
+ - Socket.connect is now much simpler - single TCPSocket.new call
487
+ - IPv4 preference logic removed (TCPSocket uses system resolver order)
488
+ - Next feature: 02-simplified-client-api.md (depends on this feature)
489
+
490
+ ---
491
+
492
+ ## New Feature Queue
493
+
494
+ | Feature | Status | Description |
495
+ |---------|--------|-------------|
496
+ | 01-tcpsocket-refactor.md | ✅ complete | Replace low-level Socket with TCPSocket |
497
+ | 02-simplified-client-api.md | ✅ complete | Blocking API, background read loop |
498
+
499
+ ---
500
+
501
+ ### Session 2025-11-29 (20)
502
+
503
+ **Feature**: 02-simplified-client-api
504
+ **Status**: Completed
505
+
506
+ **What was done**:
507
+ - Refactored `lib/yaic/client.rb` with blocking API:
508
+ - `connect` blocks until registered (001 RPL_WELCOME received)
509
+ - `join(channel)` blocks until channel appears in @channels
510
+ - `part(channel)` blocks until channel removed from @channels
511
+ - `nick(new_nick)` blocks until nick change confirmed
512
+ - `quit` cleans up, stops read thread, disconnects
513
+ - Added background read thread in `start_read_loop` that processes incoming messages
514
+ - Added thread safety: `@handlers_mutex` for on/off, `@channels_mutex` for channel access, `@state_mutex` for state
515
+ - Added `Yaic::TimeoutError` for timeout handling
516
+ - Removed `on_socket_connected` method (no longer needed)
517
+ - Simplified all integration tests to use new blocking API:
518
+ - Removed all `on_socket_connected` calls
519
+ - Removed all `wait_for_connection`, `wait_for_join`, `wait_for_part`, `drain_messages` helpers
520
+ - Tests now simply call `client.connect`, `client.join`, etc. and they block until complete
521
+ - Fixed QA issues:
522
+ - Fixed `event.raw` to `event.message` in mode tests
523
+ - Fixed `event.code` to `event.numeric` in kick tests
524
+ - Fixed race conditions by using event handlers instead of direct socket reads for oper/mode waiting
525
+ - Created comprehensive README.md with Quick Start, Events, Commands, Threading, Channel State sections
526
+ - All unit tests pass (176 runs, 351 assertions)
527
+ - Linter passes
528
+
529
+ **Notes for next session**:
530
+ - Client API is now dramatically simplified:
531
+ ```ruby
532
+ client = Yaic::Client.new(server: "irc.example.com", port: 6667, nickname: "mynick")
533
+ client.on(:message) { |event| puts event.text }
534
+ client.connect
535
+ client.join("#channel")
536
+ client.privmsg("#channel", "Hello!")
537
+ client.quit
538
+ ```
539
+ - Background thread handles all message reading and event dispatch
540
+ - All public methods are thread-safe
541
+ - README.md provides complete documentation
542
+
543
+ ### Session 2025-11-29 (21)
544
+
545
+ **Feature**: 19-fix-qa
546
+ **Status**: Completed
547
+
548
+ **What was done**:
549
+ - Added public `raw(command)` method to Client class for sending arbitrary IRC commands
550
+ - Updated all integration tests to use `client.raw()` instead of `instance_variable_get(:@socket).write()`
551
+ - Updated files: mode_test.rb, kick_test.rb, names_test.rb, nick_test.rb, join_part_test.rb, who_whois_test.rb
552
+ - Updated helper methods `become_oper` and `wait_for_mode` to use client.raw
553
+ - All tests pass (267 runs, 575 assertions, 0 failures)
554
+
555
+ **Notes for next session**:
556
+ - `client.raw(command)` is now the public way to send arbitrary IRC commands
557
+ - Integration tests no longer access internal socket via instance_variable_get
558
+ - The one remaining instance_variable_get in socket_test.rb is appropriate (testing Socket class internals)
559
+
560
+ ---
561
+
562
+ ### Session 2025-11-29 (22)
563
+
564
+ **Feature**: 20-test-optimization (Planning)
565
+ **Status**: Completed
566
+
567
+ **What was done**:
568
+ - Profiled test suite using `rake test TESTOPTS="-v"`:
569
+ - Total test time: 173 seconds
570
+ - 267 tests, 575 assertions
571
+ - 79 tests take >= 1 second (172.51s cumulative)
572
+ - Analyzed slow tests and identified root causes:
573
+ - Fixed `sleep 0.5` statements waiting for IRC responses
574
+ - `read_multiple(socket, N)` always waits full timeout
575
+ - Sequential test execution despite independent tests
576
+ - Created optimization plan at `docs/agents/ralph/plans/test-optimization-plan.md`
577
+ - Created follow-up implementation features:
578
+ - `21-test-parallelization.md` - Enable parallel test execution
579
+ - `22-wait-until-pattern.md` - Replace sleep/read_multiple with wait_until
580
+ - `23-ping-test-optimization.md` - Optimize ping test with faster server config
581
+
582
+ **Notes for next session**:
583
+ - Plan approved by user with feedback to use `wait_until` pattern
584
+ - Next feature: 21-test-parallelization.md
585
+
586
+ ---
587
+
588
+ ## Test Optimization Feature Queue
589
+
590
+ | Feature | Status | Description |
591
+ |---------|--------|-------------|
592
+ | 20-test-optimization.md | ✅ complete | Planning and profiling |
593
+ | 21-test-parallelization.md | ✅ complete | Enable parallel test execution |
594
+ | 22-wait-until-pattern.md | ✅ complete | Replace sleep/read_multiple with wait_until |
595
+ | 23-ping-test-optimization.md | ✅ complete | Optimize ping test |
596
+
597
+ ---
598
+
599
+ ### Session 2025-11-29 (23)
600
+
601
+ **Feature**: 21-test-parallelization
602
+ **Status**: Completed
603
+
604
+ **What was done**:
605
+ - Added `UniqueTestIdentifiers` module to `test/test_helper.rb` with `unique_nick` and `unique_channel` helper methods
606
+ - Helper methods generate thread-safe identifiers using `Process.pid`, `Thread.current.object_id`, and `rand`
607
+ - Added `include UniqueTestIdentifiers` and `parallelize_me!` to all 14 integration test files
608
+ - Updated all inline nick/channel generation to use the new helper methods
609
+ - Fixed variable shadowing issues in `join_part_test.rb` and `topic_test.rb`
610
+ - Test suite time reduced from ~173 seconds to ~12-17 seconds (10x speedup)
611
+ - All 267 tests pass, 575 assertions, 0 failures, 1 pre-existing skip
612
+
613
+ **Notes for next session**:
614
+ - Parallelization achieved better than expected 3-4x target (actual: 10x speedup)
615
+ - Next feature: `22-wait-until-pattern.md` - Replace sleep/read_multiple with wait_until
616
+
617
+ ---
618
+
619
+ ### Session 2025-11-29 (24)
620
+
621
+ **Feature**: 22-wait-until-pattern
622
+ **Status**: Completed
623
+
624
+ **What was done**:
625
+ - Added `wait_until(timeout: 2)` helper to `test/test_helper.rb` in UniqueTestIdentifiers module
626
+ - Replaced all `sleep 0.x` statements in integration tests with `wait_until { condition }`
627
+ - Replaced `read_multiple`, `read_until_pong`, `read_until_welcome` with inline `wait_until` blocks
628
+ - Removed all old helper functions from ping_pong_test.rb, registration_test.rb, socket_test.rb
629
+ - Removed unnecessary `require "timeout"` statements
630
+ - Refactored `become_oper` and `wait_for_mode` helpers to use `wait_until`
631
+ - Updated unit tests in client_test.rb to use deadline-based waiting
632
+ - Test suite time reduced from ~13s to ~10s (additional 30% improvement)
633
+ - All 267 tests pass, 575 assertions, 0 failures, 1 pre-existing skip
634
+
635
+ **Notes for next session**:
636
+ - `wait_until` helper returns condition result or nil on timeout
637
+ - Default timeout is 2 seconds, can be overridden with `wait_until(timeout: 5)`
638
+ - Next feature: `23-ping-test-optimization.md` - Optimize ping test with faster server config
639
+
640
+ ---
641
+
642
+ ### Session 2025-11-29 (25)
643
+
644
+ **Feature**: 23-ping-test-optimization
645
+ **Status**: Completed
646
+
647
+ **What was done**:
648
+ - Updated `test/fixtures/inspircd.conf`: Changed `pingfreq="5"` to `pingfreq="3"`
649
+ - Updated `test/integration/ping_pong_test.rb`: Reduced `wait_until(timeout: 10)` to `wait_until(timeout: 5)`
650
+ - The `sleep 6` mentioned in the spec was already replaced with `wait_until` in session 24 (feature 22)
651
+ - Ping test now completes in ~4 seconds instead of ~7 seconds
652
+ - All 267 tests pass, 575 assertions, 0 failures, 1 pre-existing skip
653
+
654
+ **Notes for next session**:
655
+ - All test optimization features (20-23) are complete
656
+ - Test suite now runs in ~10-12 seconds (down from ~173 seconds originally)
657
+ - Server must be restarted after config changes: `bin/stop-irc-server && bin/start-irc-server`
658
+
659
+ ---
660
+
661
+ ### Session 2025-11-29 (26)
662
+
663
+ **Feature**: 24-blocking-who-whois
664
+ **Status**: Completed
665
+
666
+ **What was done**:
667
+ - Created `Yaic::WhoResult` class with attributes: channel, user, host, server, nick, away, realname
668
+ - Updated `who(mask, timeout:)` to block until END OF WHO (315) and return Array of WhoResult
669
+ - Updated `whois(nick, timeout:)` to block until END OF WHOIS (318) and return WhoisResult or nil
670
+ - Added `@pending_who_results`, `@pending_who_complete`, `@pending_whois_complete` tracking hashes
671
+ - Added `handle_rpl_endofwho` handler for 315 numeric
672
+ - Added `who_reply_matches_mask?` helper for correctly matching WHO replies to pending requests (channel-based vs nick-based)
673
+ - Updated unit tests (23 tests in who_whois_test.rb)
674
+ - Updated integration tests (9 tests)
675
+ - All 279 tests pass, 629 assertions, 0 failures, 0 errors, 0 skips
676
+
677
+ **Notes for next session**:
678
+ - `client.who("#channel")` now returns Array of WhoResult objects directly
679
+ - `client.whois("nick")` now returns WhoisResult or nil directly
680
+ - Events (`:who`, `:whois`) still fire for backward compatibility
681
+ - `Yaic::TimeoutError` raised on timeout
682
+ - Next feature: 25-verbose-mode.md (depends on 24-blocking-who-whois)
683
+
684
+ ---
685
+
686
+ ### Session 2025-11-29 (27)
687
+
688
+ **Feature**: 25-verbose-mode
689
+ **Status**: Completed
690
+
691
+ **What was done**:
692
+ - Added `verbose:` parameter to `Client#initialize` (defaults to false)
693
+ - Added private `log(message)` helper method that outputs to `$stderr` with `[YAIC]` prefix
694
+ - Added logging calls to blocking operations:
695
+ - `connect`: "Connecting to host:port (SSL)..." and "Connected"
696
+ - `join`: "Joining #channel..." and "Joined #channel"
697
+ - `part`: "Parting #channel..." and "Parted #channel"
698
+ - `quit`: "Disconnected"
699
+ - `who`: "Sending WHO mask..." and "WHO complete (N results)"
700
+ - `whois`: "Sending WHOIS nick..." and "WHOIS complete"
701
+ - Unit tests: 8 new tests for verbose mode output
702
+ - Integration tests: 2 new tests for full verbose workflow
703
+ - All 289 tests pass, 671 assertions, 0 failures, 0 errors, 0 skips
704
+
705
+ **Notes for next session**:
706
+ - `verbose: true` enables debug output to stderr
707
+ - Output format: `[YAIC] <message>`
708
+ - SSL connections show "(SSL)" suffix in connect message
709
+ - All 25 planned features are now complete
710
+
711
+ ---
712
+
713
+ ## Suggested Next Feature
714
+
715
+ All features complete! The library now provides:
716
+ - IRC message parsing and serialization
717
+ - TCP/SSL connection management
718
+ - Registration (NICK, USER, PASS)
719
+ - PING/PONG handling
720
+ - Event system with handlers
721
+ - PRIVMSG/NOTICE
722
+ - JOIN/PART with channel tracking
723
+ - QUIT with disconnect events
724
+ - NICK changes with tracking
725
+ - TOPIC get/set
726
+ - KICK
727
+ - NAMES with user prefix parsing
728
+ - MODE for channels and users
729
+ - WHO/WHOIS queries (blocking with return values)
730
+ - Client API with convenience methods
731
+ - Verbose mode for debugging