opencode-skills-collection 4.0.35 → 4.0.37

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 (74) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +13 -1
  2. package/bundled-skills/agent-evaluation-reporting/SKILL.md +136 -0
  3. package/bundled-skills/agent-harness-fault-injection/SKILL.md +250 -0
  4. package/bundled-skills/anti-ui-slop/SKILL.md +115 -0
  5. package/bundled-skills/audit-agent-run-evidence/SKILL.md +165 -0
  6. package/bundled-skills/boost-asio-pro/SKILL.md +172 -0
  7. package/bundled-skills/boost-asio-pro/references/build.md +88 -0
  8. package/bundled-skills/boost-asio-pro/references/classic-boost.md +33 -0
  9. package/bundled-skills/boost-asio-pro/references/coroutines.md +415 -0
  10. package/bundled-skills/boost-asio-pro/references/pre-cpp20.md +164 -0
  11. package/bundled-skills/boost-asio-pro/references/ssl.md +38 -0
  12. package/bundled-skills/cross-platform-contract-propagation-audit/SKILL.md +159 -0
  13. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  14. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  15. package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
  16. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  17. package/bundled-skills/docs/users/aas-core.md +9 -1
  18. package/bundled-skills/docs/users/bundles.md +1 -1
  19. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  20. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  21. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  22. package/bundled-skills/docs/users/usage.md +3 -3
  23. package/bundled-skills/docs/users/visual-guide.md +4 -4
  24. package/bundled-skills/dsh-deepread/SKILL.md +200 -0
  25. package/bundled-skills/liuguang-banlan-ui/SKILL.md +135 -0
  26. package/bundled-skills/liuguang-banlan-ui/assets/starter/favicon.svg +4 -0
  27. package/bundled-skills/liuguang-banlan-ui/assets/starter/index.html +32 -0
  28. package/bundled-skills/liuguang-banlan-ui/assets/starter/launcher.css +25 -0
  29. package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/index.html +179 -0
  30. package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme-config.js +105 -0
  31. package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme.css +107 -0
  32. package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/index.html +179 -0
  33. package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme-config.js +105 -0
  34. package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme.css +107 -0
  35. package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/base.css +423 -0
  36. package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/spectral-field.js +450 -0
  37. package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/workbench.js +255 -0
  38. package/bundled-skills/liuguang-banlan-ui/references/style-contract.md +53 -0
  39. package/bundled-skills/liuguang-banlan-ui/references/verification.md +70 -0
  40. package/bundled-skills/liuguang-banlan-ui/scripts/measure_preview.py +144 -0
  41. package/bundled-skills/liuguang-banlan-ui/scripts/requirements.txt +2 -0
  42. package/bundled-skills/liuguang-banlan-ui/scripts/scaffold_template.py +43 -0
  43. package/bundled-skills/liuguang-banlan-ui/scripts/validate_manifest.py +173 -0
  44. package/bundled-skills/multi-source-search/SKILL.md +139 -0
  45. package/bundled-skills/multi-source-search/references/report-schema.md +47 -0
  46. package/bundled-skills/multi-source-search/scripts/validate_report.py +221 -0
  47. package/bundled-skills/review-multi-agent-orchestration/SKILL.md +201 -0
  48. package/bundled-skills/ui-slop-score/SKILL.md +80 -0
  49. package/bundled-skills/using-lwc/LICENSE +201 -0
  50. package/bundled-skills/using-lwc/README.md +926 -0
  51. package/bundled-skills/using-lwc/README.zh-CN.md +836 -0
  52. package/bundled-skills/using-lwc/SKILL.md +156 -0
  53. package/bundled-skills/using-lwc/agents/openai.yaml +4 -0
  54. package/bundled-skills/using-lwc/assets/global-purpose.md +17 -0
  55. package/bundled-skills/using-lwc/assets/global-schema.md +28 -0
  56. package/bundled-skills/using-lwc/references/active-memory.md +81 -0
  57. package/bundled-skills/using-lwc/references/agent-onboarding.md +84 -0
  58. package/bundled-skills/using-lwc/references/code-graph.md +60 -0
  59. package/bundled-skills/using-lwc/references/core-memory.md +51 -0
  60. package/bundled-skills/using-lwc/references/document-conversion.md +53 -0
  61. package/bundled-skills/using-lwc/references/document-graph.md +58 -0
  62. package/bundled-skills/using-lwc/references/llm-wiki.md +75 -0
  63. package/bundled-skills/using-lwc/references/memory-policy.md +551 -0
  64. package/bundled-skills/using-lwc/references/operations-manual.md +503 -0
  65. package/bundled-skills/using-lwc/references/recovery-maintenance.md +53 -0
  66. package/bundled-skills/using-lwc/references/strong-context.md +54 -0
  67. package/bundled-skills/using-lwc/references/trigger-playbook.md +53 -0
  68. package/bundled-skills/using-lwc/references/word-graph.md +45 -0
  69. package/bundled-skills/using-lwc/scripts/bootstrap.sh +359 -0
  70. package/bundled-skills/using-lwc/scripts/install-lwc.sh +172 -0
  71. package/bundled-skills/youtube-summarizer/SKILL.md +21 -7
  72. package/bundled-skills/youtube-summarizer/scripts/extract-transcript.py +45 -12
  73. package/package.json +3 -2
  74. package/skills_index.json +268 -0
@@ -0,0 +1,415 @@
1
+ # C++20 Coroutine Style (Boost ≥ 1.77)
2
+
3
+ The preferred style when the toolchain allows it. Read `SKILL.md` first — the rules there (write queue, buffer lifetime, version floors) apply here and are not repeated.
4
+
5
+ ## Core Architecture
6
+
7
+ Boost.Asio uses the **Proactor pattern**: async operations run in the background, completion handlers are invoked with results.
8
+
9
+ ```
10
+ Program → I/O Object → Execution Context → OS → (completion) → Handler
11
+ ```
12
+
13
+ **Execution contexts:** `io_context` (single/multi-thread event loop), `thread_pool`, `system_context`
14
+
15
+ **I/O objects:** `tcp::socket`, `tcp::acceptor`, `udp::socket`, `steady_timer`, `ssl::stream<>`
16
+
17
+ **Completion tokens:** Control how async results are delivered — `use_awaitable`, `deferred` (default), `detached`, callbacks, futures.
18
+
19
+ ## C++20 Coroutines (Preferred Style)
20
+
21
+ ```cpp
22
+ #include <boost/asio.hpp>
23
+ #include <boost/asio/co_spawn.hpp>
24
+ #include <boost/asio/use_awaitable.hpp>
25
+
26
+ namespace asio = boost::asio;
27
+ using tcp = asio::ip::tcp;
28
+
29
+ asio::awaitable<void> echo_session(tcp::socket socket) {
30
+ try {
31
+ char data[1024];
32
+ for (;;) {
33
+ std::size_t n = co_await socket.async_read_some(asio::buffer(data));
34
+ co_await async_write(socket, asio::buffer(data, n));
35
+ }
36
+ } catch (std::exception&) {
37
+ // Connection closed or error — coroutine ends
38
+ }
39
+ }
40
+
41
+ asio::awaitable<void> listener(tcp::acceptor acceptor) {
42
+ for (;;) {
43
+ auto socket = co_await acceptor.async_accept();
44
+ co_spawn(acceptor.get_executor(), echo_session(std::move(socket)), asio::detached);
45
+ }
46
+ }
47
+
48
+ int main() {
49
+ asio::io_context io(1); // concurrency_hint=1 for single-threaded
50
+ tcp::acceptor acceptor(io, {tcp::v4(), 8080});
51
+ co_spawn(io, listener(std::move(acceptor)), asio::detached);
52
+ io.run();
53
+ }
54
+ ```
55
+
56
+ **Key rules:**
57
+ - `co_spawn(executor, coroutine, completion_token)` launches a coroutine
58
+ - Without explicit token, async ops use `deferred` (returns awaitable object for `co_await`)
59
+ - Errors become `system_error` exceptions by default inside coroutines
60
+ - Use `asio::detached` when you don't need the coroutine's result
61
+
62
+ ## Error Handling in Coroutines
63
+
64
+ **Default:** Errors throw `boost::system::system_error`.
65
+
66
+ **Explicit error handling with `as_tuple`:**
67
+ ```cpp
68
+ auto [ec, n] = co_await socket.async_read_some(
69
+ asio::buffer(data), asio::as_tuple(asio::use_awaitable));
70
+ if (ec) { /* handle error, no exception */ }
71
+ ```
72
+ **Wrap, don't use bare `as_tuple`.** Always write `as_tuple(use_awaitable)`. Bare `asio::as_tuple` resolves against the operation's *default* completion token (often `deferred`), which compiles in some contexts but fails in others — wrapping an explicit base token is unambiguous everywhere.
73
+
74
+ **With `redirect_error`:**
75
+ ```cpp
76
+ boost::system::error_code ec;
77
+ std::size_t n = co_await socket.async_read_some(
78
+ asio::buffer(data), asio::redirect_error(asio::use_awaitable, ec));
79
+ ```
80
+
81
+ ## Strands (Thread Safety)
82
+
83
+ **Rule: All async operations on a shared object MUST execute on the same strand.**
84
+
85
+ ```cpp
86
+ // Per-connection strand
87
+ asio::strand<asio::io_context::executor_type> strand(io.get_executor());
88
+ co_spawn(strand, session(std::move(socket)), asio::detached);
89
+
90
+ // Bind handler to strand
91
+ socket.async_read_some(asio::buffer(data),
92
+ asio::bind_executor(strand, [](error_code ec, size_t n) { /*...*/ }));
93
+ ```
94
+
95
+ **Implicit strands (no explicit strand needed):**
96
+ - Single-threaded `io_context::run()` — all handlers are sequential
97
+ - Single chain of async ops on one connection (half-duplex)
98
+
99
+ **Explicit strand required when:**
100
+ - Multiple threads call `io_context::run()`
101
+ - Full-duplex read+write on same socket
102
+ - Shared state accessed from multiple async chains
103
+
104
+ ## Full-Duplex: Strand + Write Queue
105
+
106
+ **A strand serializes handler *execution*, NOT whole composed operations.** Two `async_write`s started "concurrently" on the same strand still overlap and **interleave bytes on the wire** — the strand only orders the intermediate handlers, not the byte stream. For full-duplex (a read loop plus pushes/replies writing at the same time on one socket), a strand alone is **not** enough: you must serialize outbound writes yourself with a queue.
107
+
108
+ ```cpp
109
+ // Give each accepted socket its OWN strand, then run every chain (read loop,
110
+ // pushes, replies) on that strand. Passing an executor to async_accept means you
111
+ // must ALSO pass an explicit completion token — the default-deferred shortcut on
112
+ // the zero-arg form no longer applies.
113
+ auto socket = co_await acceptor.async_accept(asio::make_strand(io), asio::use_awaitable);
114
+ std::make_shared<connection>(std::move(socket))->start();
115
+
116
+ class connection : public std::enable_shared_from_this<connection> {
117
+ tcp::socket socket_; // bound to its own strand
118
+ std::deque<std::string> outbox_;
119
+ bool writing_ = false;
120
+ public:
121
+ explicit connection(tcp::socket s) : socket_(std::move(s)) {}
122
+
123
+ void start() {
124
+ // Each chain captures `self` so the connection outlives all its coroutines.
125
+ co_spawn(socket_.get_executor(),
126
+ [self = shared_from_this()] { return self->read_loop(); }, asio::detached);
127
+ }
128
+
129
+ // Call ONLY from the connection's strand (e.g. from its own coroutines).
130
+ // From another thread/strand: asio::dispatch(socket_.get_executor(), ...).
131
+ void send(std::string frame) {
132
+ outbox_.push_back(std::move(frame));
133
+ if (!writing_)
134
+ co_spawn(socket_.get_executor(),
135
+ [self = shared_from_this()] { return self->write_loop(); }, asio::detached);
136
+ }
137
+ private:
138
+ asio::awaitable<void> write_loop() {
139
+ writing_ = true;
140
+ while (!outbox_.empty()) {
141
+ co_await async_write(socket_, asio::buffer(outbox_.front()));
142
+ outbox_.pop_front(); // pop only AFTER the write completes
143
+ }
144
+ writing_ = false;
145
+ }
146
+ asio::awaitable<void> read_loop(); // reads frames, calls send() for replies
147
+ };
148
+ ```
149
+
150
+ **Why each rule matters:**
151
+ - One strand per connection → read loop and write loop never run their handlers concurrently.
152
+ - Write queue + `writing_` flag → at most one `async_write` in flight, so frames never interleave.
153
+ - `enable_shared_from_this` + capturing `self` in every `co_spawn` → the connection survives until all of its read/write/timer chains finish.
154
+ - The accepted socket from `async_accept(make_strand(...))` is `basic_stream_socket<tcp, strand<...>>`, **not** `tcp::socket`. Take it **by value** (`connection(tcp::socket s)`, store `tcp::socket socket_`) — the strand executor type-erases into `any_io_executor` on the move. Passing that accepted socket to a `tcp::socket&` (by reference) instead will **fail to compile** — use `auto` or accept by value.
155
+
156
+ **Strand from inside a coroutine** (when `io` isn't a captured local): get the executor from the coroutine and make a strand off it — no `io_context&` needed:
157
+ ```cpp
158
+ auto ex = co_await asio::this_coro::executor;
159
+ auto socket = co_await acceptor.async_accept(asio::make_strand(ex), asio::use_awaitable);
160
+ ```
161
+
162
+ **Run the read loop and idle watch together** — two `awaitable<void>` branches; don't inspect the result, the first to finish unwinds the other:
163
+ ```cpp
164
+ using namespace asio::experimental::awaitable_operators;
165
+ co_await (read_loop() || idle_watch(socket_, timer_)); // either returning tears down the connection
166
+ ```
167
+
168
+ **Stopping a detached side-coroutine** (e.g. a per-symbol ticker that must end on unsubscribe/close): a detached `co_spawn` won't stop itself. Either (a) have its loop re-check a flag each iteration and `co_return` when gone:
169
+ ```cpp
170
+ while (subscriptions_.contains(symbol) && socket_.is_open()) {
171
+ timer.expires_after(250ms);
172
+ co_await timer.async_wait(asio::as_tuple(asio::use_awaitable));
173
+ if (/* still subscribed */) send(make_tick(symbol));
174
+ }
175
+ ```
176
+ or (b) spawn it with a `cancellation_signal` and `emit()` cancellation on unsubscribe. The flag approach is simpler for per-subscription tickers.
177
+
178
+ ## Timers and Timeouts
179
+
180
+ ```cpp
181
+ asio::awaitable<void> with_timeout(tcp::socket& socket) {
182
+ asio::steady_timer timer(co_await asio::this_coro::executor);
183
+ timer.expires_after(std::chrono::seconds(30));
184
+
185
+ // Race: read vs timeout (requires awaitable_operators)
186
+ using namespace asio::experimental::awaitable_operators;
187
+
188
+ auto result = co_await (
189
+ socket.async_read_some(asio::buffer(data), asio::use_awaitable)
190
+ || timer.async_wait(asio::use_awaitable)
191
+ );
192
+
193
+ if (result.index() == 0) { /* read completed */ }
194
+ else { /* timeout — cancel the socket */ socket.close(); }
195
+ }
196
+ ```
197
+
198
+ **Re-armable idle timeout** (reset on every received frame — the common server pattern):
199
+ ```cpp
200
+ // Run as a long-lived parallel branch. Calling expires_after() again cancels the
201
+ // pending wait, resolving the in-flight async_wait with operation_aborted — that
202
+ // is the signal to keep waiting, NOT an error. Genuine expiry resolves with no error.
203
+ asio::awaitable<void> idle_watch(tcp::socket& sock, asio::steady_timer& timer) {
204
+ for (;;) {
205
+ auto [ec] = co_await timer.async_wait(asio::as_tuple(asio::use_awaitable));
206
+ if (ec == asio::error::operation_aborted) continue; // re-armed → keep waiting
207
+ if (ec) co_return; // timer error
208
+ sock.close(); // real timeout fired
209
+ co_return;
210
+ }
211
+ }
212
+ // On every frame received from the peer: timer.expires_after(30s);
213
+ ```
214
+
215
+ **Parallel operations (`&&` and `||`):**
216
+ ```cpp
217
+ #include <boost/asio/experimental/awaitable_operators.hpp>
218
+ using namespace asio::experimental::awaitable_operators;
219
+
220
+ // Wait for both (AND) — cancels other on failure
221
+ auto [read_n, write_n] = co_await (
222
+ async_read(sock, in_buf, use_awaitable) &&
223
+ async_write(sock, out_buf, use_awaitable)
224
+ );
225
+
226
+ // Wait for first (OR) — cancels other on success
227
+ auto result = co_await (
228
+ async_read(sock, buf, use_awaitable) ||
229
+ timer.async_wait(use_awaitable)
230
+ );
231
+ ```
232
+
233
+ **Note:** `||` and `&&` operators require explicit `use_awaitable` token, and the `awaitable_operators.hpp` header (Boost ≥ 1.77 — see the version floors in SKILL.md).
234
+
235
+ **Void branches:** when a branch returns `void` (e.g. two `awaitable<void>` chains), that arm contributes `std::monostate` to the result variant. If *both* branches are void the result is `variant<monostate, monostate>` — don't inspect `.index()`; just `co_await` the expression and let whichever finishes first unwind the other.
236
+
237
+ ## Cancellation
238
+
239
+ ```cpp
240
+ asio::awaitable<void> cancellable_work() {
241
+ // Check cancellation state
242
+ auto cs = co_await asio::this_coro::cancellation_state;
243
+ if (cs.cancelled() != asio::cancellation_type::none) {
244
+ co_return;
245
+ }
246
+
247
+ // Enable cancellation types
248
+ co_await asio::this_coro::reset_cancellation_state(
249
+ asio::enable_total_cancellation());
250
+ }
251
+ ```
252
+
253
+ ## TCP Server Pattern
254
+
255
+ ```cpp
256
+ asio::awaitable<void> server(asio::io_context& io, unsigned short port) {
257
+ tcp::acceptor acceptor(io, {tcp::v4(), port});
258
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
259
+
260
+ for (;;) {
261
+ auto socket = co_await acceptor.async_accept();
262
+ co_spawn(
263
+ io.get_executor(), // or a strand for multi-threaded
264
+ handle_client(std::move(socket)),
265
+ [](std::exception_ptr ep) {
266
+ if (ep) std::rethrow_exception(ep);
267
+ }
268
+ );
269
+ }
270
+ }
271
+ ```
272
+
273
+ ## Buffers
274
+
275
+ | Type | Use |
276
+ |------|-----|
277
+ | `asio::buffer(data, size)` | Wrap existing memory (no ownership) |
278
+ | `asio::dynamic_buffer(vec)` | Growable buffer over `vector`/`string` |
279
+ | `asio::streambuf` | Legacy stream buffer |
280
+ | `asio::const_buffer` | Read-only view |
281
+ | `asio::mutable_buffer` | Writable view |
282
+
283
+ **Critical:** `asio::buffer()` does NOT own memory. The underlying storage must outlive the async operation.
284
+
285
+ ## Resolver (DNS)
286
+
287
+ ```cpp
288
+ asio::awaitable<void> connect_to(asio::io_context& io,
289
+ std::string host, std::string port) {
290
+ tcp::resolver resolver(io);
291
+ auto endpoints = co_await resolver.async_resolve(host, port);
292
+
293
+ tcp::socket socket(io);
294
+ co_await asio::async_connect(socket, endpoints);
295
+ // socket is now connected
296
+ }
297
+ ```
298
+
299
+ ## Multi-Threaded io_context
300
+
301
+ ```cpp
302
+ asio::io_context io;
303
+ std::vector<std::thread> threads;
304
+
305
+ for (int i = 0; i < std::thread::hardware_concurrency(); ++i) {
306
+ threads.emplace_back([&io] { io.run(); });
307
+ }
308
+
309
+ // All handlers MUST be strand-protected when sharing state
310
+ for (auto& t : threads) t.join();
311
+ ```
312
+
313
+ ## Composed Async Operations (Custom)
314
+
315
+ ```cpp
316
+ template <typename CompletionToken>
317
+ auto async_echo(tcp::socket& socket, CompletionToken&& token) {
318
+ return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
319
+ asio::co_composed<void(boost::system::error_code)>(
320
+ [](auto state, tcp::socket& socket) -> void {
321
+ state.throw_if_cancelled(true);
322
+ state.reset_cancellation_state(asio::enable_terminal_cancellation());
323
+ try {
324
+ char data[1024];
325
+ for (;;) {
326
+ std::size_t n = co_await socket.async_read_some(asio::buffer(data));
327
+ co_await async_write(socket, asio::buffer(data, n));
328
+ }
329
+ } catch (const boost::system::system_error& e) {
330
+ co_return {e.code()};
331
+ }
332
+ }, socket),
333
+ token, std::ref(socket));
334
+ }
335
+ ```
336
+
337
+ ## Line-Based Protocols
338
+
339
+ For newline-delimited protocols, prefer `async_read_until` over manual `async_read_some` + buffer parsing:
340
+
341
+ ```cpp
342
+ asio::awaitable<void> line_echo(tcp::socket socket) {
343
+ asio::streambuf buf;
344
+ for (;;) {
345
+ std::size_t n = co_await asio::async_read_until(socket, buf, '\n');
346
+ std::string line(asio::buffers_begin(buf.data()),
347
+ asio::buffers_begin(buf.data()) + n);
348
+ buf.consume(n);
349
+ co_await async_write(socket, asio::buffer(line));
350
+ }
351
+ }
352
+ ```
353
+
354
+ Or with `dynamic_buffer` over a `std::string`:
355
+ ```cpp
356
+ std::string buf;
357
+ std::size_t n = co_await asio::async_read_until(socket, asio::dynamic_buffer(buf), '\n');
358
+ std::string line = buf.substr(0, n);
359
+ buf.erase(0, n);
360
+ ```
361
+
362
+ ## Length-Prefixed Binary Framing
363
+
364
+ For binary protocols, read the fixed-size header fully, then the body fully — two sequential **composed** reads (`async_read` fills the whole buffer, handling short reads). Do NOT use `async_read_some` for framing.
365
+
366
+ ```cpp
367
+ // Frame: [4-byte big-endian length N][N-byte body]
368
+ asio::awaitable<std::string> read_frame(tcp::socket& sock) {
369
+ constexpr uint32_t max_frame_size = 16 * 1024 * 1024;
370
+ uint32_t len_be = 0;
371
+ co_await async_read(sock, asio::buffer(&len_be, sizeof len_be)); // exactly 4 bytes
372
+ uint32_t n = ntohl(len_be); // <arpa/inet.h>; or hand-roll endian swap
373
+ if (n > max_frame_size) {
374
+ throw std::length_error("frame exceeds 16 MiB limit"); // <stdexcept>
375
+ }
376
+ std::string body(n, '\0');
377
+ co_await async_read(sock, asio::buffer(body)); // exactly n bytes
378
+ co_return body;
379
+ }
380
+
381
+ asio::awaitable<void> write_frame(tcp::socket& sock, std::string_view body) {
382
+ uint32_t len_be = htonl(static_cast<uint32_t>(body.size()));
383
+ std::array<asio::const_buffer, 2> bufs{
384
+ asio::buffer(&len_be, sizeof len_be), asio::buffer(body)};
385
+ co_await async_write(sock, bufs); // gather-write header + body atomically
386
+ // len_be and body must outlive the write — they do here (co_await suspends in-frame).
387
+ }
388
+ ```
389
+
390
+ ## Graceful Shutdown (signal_set)
391
+
392
+ ```cpp
393
+ asio::signal_set signals(io, SIGINT, SIGTERM);
394
+ signals.async_wait([&](const boost::system::error_code&, int /*signo*/) {
395
+ acceptor.close(); // stop accepting; let in-flight sessions drain, then io.run() returns
396
+ // or, for an immediate stop: io.stop();
397
+ });
398
+ ```
399
+
400
+ For coroutine-style shutdown, `co_await signals.async_wait()` in a dedicated coroutine instead of a callback.
401
+
402
+ ## Quick Reference
403
+
404
+ | Operation | Function |
405
+ |-----------|----------|
406
+ | Launch coroutine | `co_spawn(executor, coro, token)` |
407
+ | Accept connection | `co_await acceptor.async_accept()` |
408
+ | Read some bytes | `co_await socket.async_read_some(buffer)` |
409
+ | Read exact/until | `co_await async_read(stream, buf)` / `async_read_until(stream, buf, delim)` |
410
+ | Write all | `co_await async_write(stream, buffer)` |
411
+ | Connect | `co_await async_connect(socket, endpoints)` |
412
+ | Resolve DNS | `co_await resolver.async_resolve(host, port)` |
413
+ | Wait timer | `co_await timer.async_wait()` |
414
+ | TLS handshake | `co_await stream.async_handshake(type)` |
415
+ | Get executor | `co_await asio::this_coro::executor` |
@@ -0,0 +1,164 @@
1
+ # Pre-C++20 Styles (C++11–17, Boost ≥ 1.74)
2
+
3
+
4
+ If you can't use C++20 `co_await`, the **same Asio library** (modern Boost or standalone) still works — only the *async style* changes. Compile with C++11 or later. Two pre-C++20 styles:
5
+
6
+ 1. **Completion handlers (callbacks)** — header-only, C++11, no extra dependencies. The recommended baseline.
7
+ 2. **Stackful coroutines** (`asio::spawn` + `yield_context`) — synchronous-looking like `co_await`, but built on Boost.Coroutine/Boost.Context, so it **must be linked** (not header-only) — see build note below.
8
+
9
+ **Unchanged from the coroutine style** (these are library, not language, features): `io_context`, `make_strand`, `bind_executor`, `steady_timer`, `ssl::stream`, `signal_set`, `async_read`/`async_write`/`async_read_until`, buffers, `resolver`. Use them exactly as shown in [coroutines.md](coroutines.md).
10
+
11
+ **Not available pre-C++20:** `co_await`/`awaitable<T>`, `co_spawn`, `use_awaitable`, the `||`/`&&` `awaitable_operators`, `as_tuple`, and `co_composed`. The table below gives the equivalent.
12
+
13
+ ### C++20 → pre-C++20 mapping
14
+
15
+ | C++20 coroutine | Pre-C++20 equivalent |
16
+ |-----------------|----------------------|
17
+ | `co_await op(use_awaitable)` | callback: `op(handler)` · stackful: `op(yield)` |
18
+ | `awaitable<T>` function | member `do_x()` callback chain · or `spawn(strand, fn)` |
19
+ | `co_spawn(ex, coro, tok)` | start the callback chain · or `asio::spawn(ex, fn, tok)` |
20
+ | `as_tuple(use_awaitable)` → `[ec,n]` | callback's `(ec, n)` params · stackful: `op(yield[ec])` |
21
+ | `a() \|\| b()` (first-wins race) | a **watchdog timer** that closes the socket; the other op fails with `operation_aborted` |
22
+ | `a() && b()` (wait both) | launch both, count completions in a shared `shared_ptr<int>` |
23
+ | `co_composed<>` custom op | `asio::async_compose<>` (C++11) |
24
+ | `co_await this_coro::executor` | `socket_.get_executor()` / a passed-in executor |
25
+
26
+ ### Callback style: full-duplex + write queue
27
+
28
+ The full-duplex write-queue rule is identical — a strand alone doesn't stop interleaved writes — just expressed with chained handlers. Capture `self = shared_from_this()` in **every** handler to keep the connection alive.
29
+
30
+ ```cpp
31
+ class connection : public std::enable_shared_from_this<connection> {
32
+ tcp::socket socket_;
33
+ asio::strand<asio::any_io_executor> strand_; // tcp::socket's executor is any_io_executor
34
+ std::deque<std::string> outbox_;
35
+ bool writing_ = false;
36
+ char buf_[1024];
37
+ public:
38
+ explicit connection(tcp::socket s)
39
+ : socket_(std::move(s)), strand_(asio::make_strand(socket_.get_executor())) {}
40
+ void start() { do_read(); }
41
+
42
+ void send(std::string frame) { // call on the strand only
43
+ outbox_.push_back(std::move(frame));
44
+ if (!writing_) do_write();
45
+ }
46
+ private:
47
+ void do_read() {
48
+ auto self = shared_from_this();
49
+ socket_.async_read_some(asio::buffer(buf_),
50
+ asio::bind_executor(strand_, // serialize handler execution
51
+ [this, self](boost::system::error_code ec, std::size_t n) {
52
+ if (ec) return; // self drops here → socket closes
53
+ /* parse buf_[0..n]; call send() for replies */
54
+ do_read();
55
+ }));
56
+ }
57
+ void do_write() { // at most one async_write in flight
58
+ writing_ = true;
59
+ auto self = shared_from_this();
60
+ asio::async_write(socket_, asio::buffer(outbox_.front()),
61
+ asio::bind_executor(strand_,
62
+ [this, self](boost::system::error_code ec, std::size_t) {
63
+ if (ec) { writing_ = false; return; }
64
+ outbox_.pop_front();
65
+ if (!outbox_.empty()) do_write();
66
+ else writing_ = false;
67
+ }));
68
+ }
69
+ };
70
+ ```
71
+
72
+ ### Stackful style: spawn + yield_context
73
+
74
+ `yield` is a completion token: `op(socket, ..., yield)` suspends until done and returns the result; errors **throw** by default, or use `yield[ec]` for an `error_code`. Run each chain on a per-connection strand.
75
+
76
+ ```cpp
77
+ asio::spawn(strand, // executor or strand
78
+ [self](asio::yield_context yield) { // capture self for lifetime
79
+ try {
80
+ char data[1024];
81
+ for (;;) {
82
+ std::size_t n = self->socket_.async_read_some(asio::buffer(data), yield);
83
+ asio::async_write(self->socket_, asio::buffer(data, n), yield);
84
+ }
85
+ } catch (const std::exception&) { self->socket_.close(); }
86
+ },
87
+ asio::detached); // completion token (3rd arg)
88
+ ```
89
+
90
+ ### Timeout without `||` (watchdog timer)
91
+
92
+ Replace the `read || timer` race with a separate watchdog: reset the timer on each read; a second chain waits on it and closes the socket on expiry, which makes the read fail with `operation_aborted`.
93
+
94
+ ```cpp
95
+ // callback watchdog
96
+ void arm_timeout() {
97
+ timer_.expires_after(std::chrono::seconds(30));
98
+ auto self = shared_from_this();
99
+ timer_.async_wait(asio::bind_executor(strand_,
100
+ [this, self](boost::system::error_code ec) {
101
+ if (!ec) socket_.close(); // fired → drop; reset cancels with ec
102
+ }));
103
+ }
104
+ // call arm_timeout() again on every frame received to re-arm
105
+ ```
106
+
107
+ ### Callback multi-step reads + recurring side-tasks
108
+
109
+ **Lifetime shift:** coroutine *stack locals* become *member variables* in callback style — a header/body buffer must outlive each async op or it dangles. Chain a composed read of the length, then the body:
110
+
111
+ ```cpp
112
+ // members, NOT locals — they must survive until the handler runs
113
+ uint32_t len_be_;
114
+ std::string body_;
115
+
116
+ void read_frame() {
117
+ auto self = shared_from_this();
118
+ asio::async_read(socket_, asio::buffer(&len_be_, sizeof len_be_),
119
+ asio::bind_executor(strand_, [this, self](boost::system::error_code ec, std::size_t) {
120
+ if (ec) return;
121
+ body_.assign(ntohl(len_be_), '\0');
122
+ asio::async_read(socket_, asio::buffer(body_), // read exactly N bytes
123
+ asio::bind_executor(strand_, [this, self](boost::system::error_code ec2, std::size_t) {
124
+ if (ec2) return;
125
+ handle_frame(body_); // dispatch on type byte
126
+ read_frame(); // next frame
127
+ }));
128
+ }));
129
+ }
130
+ ```
131
+
132
+ **Recurring side-task** (e.g. push every 250ms) running concurrently with the read loop — there is no detached coroutine to stop, so use a self-rescheduling timer and `cancel()` it to stop:
133
+
134
+ ```cpp
135
+ void schedule_tick(std::string symbol, std::shared_ptr<asio::steady_timer> t) {
136
+ t->expires_after(std::chrono::milliseconds(250));
137
+ auto self = shared_from_this();
138
+ t->async_wait(asio::bind_executor(strand_,
139
+ [this, self, symbol, t](boost::system::error_code ec) {
140
+ if (ec) return; // cancelled on unsubscribe/close → stops
141
+ send(make_tick(symbol)); // enqueue on the write queue
142
+ schedule_tick(symbol, t); // reschedule itself
143
+ }));
144
+ }
145
+ // start: keep one timer per subscription alive (e.g. in a map); stop: erase + t->cancel()
146
+ ```
147
+
148
+ ### Build difference (stackful spawn only)
149
+
150
+ Callbacks need no change beyond the standard (drop `-fcoroutines`; it's only for C++20 `co_await`):
151
+ ```cmake
152
+ set(CMAKE_CXX_STANDARD 11) # or 14 / 17
153
+ set(CMAKE_CXX_EXTENSIONS OFF) # else CMake emits -std=gnu++NN, not literal -std=c++NN
154
+ target_link_libraries(app PRIVATE Boost::headers Threads::Threads)
155
+ target_compile_definitions(app PRIVATE BOOST_ERROR_CODE_HEADER_ONLY)
156
+ ```
157
+ Stackful `spawn` additionally requires Boost.Coroutine (which uses Boost.Context) — **not header-only**:
158
+ ```cmake
159
+ find_package(Boost REQUIRED COMPONENTS coroutine)
160
+ target_link_libraries(app PRIVATE Boost::coroutine) # pulls in Boost.Context
161
+ ```
162
+ > Standalone Asio's `spawn` also depends on Boost.Coroutine/Context — it drags Boost into an otherwise Boost-free build. If you want zero Boost, use the **callback** style.
163
+ >
164
+ > The 3-arg `spawn(ex, fn, token)` form needs **Boost ≥ 1.80** (older Boost has only `spawn(ex, fn)`). On old distros like Debian bookworm (Boost 1.74), the **callback** style compiles cleanly while stackful `spawn` does not — verified.
@@ -0,0 +1,38 @@
1
+ # SSL/TLS
2
+
3
+ Applies to every style — `ssl::stream<>` is a library feature, not a language one.
4
+
5
+
6
+ ```cpp
7
+ #include <boost/asio.hpp>
8
+ #include <boost/asio/ssl.hpp>
9
+
10
+ namespace asio = boost::asio;
11
+ namespace ssl = asio::ssl;
12
+ using tcp = asio::ip::tcp;
13
+
14
+ asio::awaitable<void> tls_client(asio::io_context& io) {
15
+ ssl::context ctx(ssl::context::tlsv13_client);
16
+ ctx.set_default_verify_paths();
17
+
18
+ ssl::stream<tcp::socket> stream(io, ctx);
19
+
20
+ // Connect underlying TCP socket
21
+ auto& sock = stream.lowest_layer();
22
+ co_await sock.async_connect(endpoint);
23
+
24
+ // Set SNI hostname (required for most servers)
25
+ SSL_set_tlsext_host_name(stream.native_handle(), "example.com");
26
+ stream.set_verify_mode(ssl::verify_peer);
27
+ stream.set_verify_callback(ssl::host_name_verification("example.com"));
28
+
29
+ // TLS handshake
30
+ co_await stream.async_handshake(ssl::stream_base::client);
31
+
32
+ // Read/write as normal stream
33
+ co_await async_write(stream, asio::buffer(request));
34
+ co_await async_read_until(stream, response_buf, "\r\n");
35
+ }
36
+ ```
37
+
38
+ **Critical:** SSL streams require strand-based synchronization for all async operations — no concurrent reads/writes without a strand.