ai: fold in ramekin outbox proposals
Assisted-by: Claude Opus 5.5 via Claude Code
change nzkwwslwrmuvoxutoqlsmpzvuvqmzvvp
commit 4696f30a735e35b6f88493a0c516453df43aaef5
author Alpha Chen <alpha@kejadlen.dev>
date
parent optvwxnl
diff --git a/ai/skills/design-notes/SKILL.md b/ai/skills/design-notes/SKILL.md
index 909a6b1a..9a7f1112 100644
--- a/ai/skills/design-notes/SKILL.md
+++ b/ai/skills/design-notes/SKILL.md
@@ -9,6 +9,10 @@ Supplementary notes on designing software with AI, accumulated from experience a
 
 *This is a self-improving skill — see the `self-improving-skills` skill.*
 
+## Before proposing: read the existing record
+
+If the task points at a design record, or the repo already keeps them, read it first. Implementation conforms to the record, not the reverse, and a dated record is never rewritten to match behavior that arrived after it.
+
 ## The implementation trap
 
 AI collapses design and implementation into a single step. Describe a feature, receive hundreds of lines — with every architectural decision made silently. The first time you see the AI's design thinking is when you're reading code, which is the most expensive place to discover a disagreement.
diff --git a/ai/skills/ruby-style/SKILL.md b/ai/skills/ruby-style/SKILL.md
index 35cc74c2..aeec9109 100644
--- a/ai/skills/ruby-style/SKILL.md
+++ b/ai/skills/ruby-style/SKILL.md
@@ -18,6 +18,7 @@ immediately instead of propagating `nil` through the code.
 # good
 config.fetch(:timeout)
 config.fetch(:timeout, 30)
+config.fetch(:timeout) { expensive_default }
 
 # bad — silent nil on typos or missing keys
 config[:timeout]
@@ -68,12 +69,16 @@ and composes naturally with any iterator.
 ```ruby
 # good — compose onto the base enumerator
 array.each.with_index { |item, i| ... }
+array.map.with_index { |item, i| ... }
+array.flat_map.with_index { |item, i| ... }
 array.each.with_object([]) { |item, acc| ... }
 hash.each { |key, value| ... }
 
 # bad — specialized methods
 array.each_with_index { |item, i| ... }
 array.each_with_object([]) { |item, acc| ... }
+hash.each_value { |value| ... }
+hash.each_key { |key| ... }
 ```
 
 The same goes for the specialized `each_*` readers (`each_value`,
@@ -160,6 +165,10 @@ File.expand_path("../secrets.yml", "/app/config/database.yml")
 
 Use `File` for plain IO when `Pathname` adds nothing.
 
+`system`, `exec`, and Rake's `sh` reject a `Pathname` with a
+`TypeError` — they want a `String`. Convert at that boundary:
+`sh "ls", path.to_s`.
+
 ## Building hashes
 
 Prefer `.to_h` with a block over `.each_with_object` or manual
@@ -292,6 +301,11 @@ Literals in files with no magic comment are *chilled*: mutating one
 warns instead of raising. Run with `RUBYOPT=-W:deprecated` to find them,
 then `.dup` each one.
 
+Don't fight an existing convention. If a project already relies on the
+magic comment — RuboCop's `Style/FrozenStringLiteralComment` is
+enabled, or the files already carry it — keep adding it so the codebase
+stays consistent.
+
 ## Requires
 
 Put `require` calls at the top of the file. Defer one inside a method
diff --git a/ai/skills/rust/scaffolding.md b/ai/skills/rust/scaffolding.md
index f8275823..3f057135 100644
--- a/ai/skills/rust/scaffolding.md
+++ b/ai/skills/rust/scaffolding.md
@@ -80,11 +80,12 @@ Notes on this template:
   detail survives through tests and library consumers.
 - `hegeltest` — property-based testing built on the Hypothesis engine,
   with built-in shrinking.
-- `[lints.clippy]` — `self_named_module_files` enforces `foo.rs` over
-  `foo/mod.rs` for module files. The rest are panic-discipline lints:
-  `unwrap`, `expect`, raw `panic!`, slice indexing, and unchecked
-  arithmetic are denial-of-service bugs waiting for untrusted input. All
-  are lint levels, so they live here rather than in `.clippy.toml`. See
+- `[lints.clippy]` — `self_named_module_files` enforces `foo/mod.rs`
+  over `foo.rs` for directory modules (P-MOD in `style.md`). The rest
+  are panic-discipline lints: `unwrap`, `expect`, raw `panic!`, slice
+  indexing, and unchecked arithmetic are denial-of-service bugs waiting
+  for untrusted input. All are lint levels, so they live here rather
+  than in `.clippy.toml`. See
   `style.md` "Panic discipline" for rationale and how to opt out at a
   single call site when an invariant is genuinely local.
 - A project that's already a multi-crate workspace hoists the block to
diff --git a/ai/skills/rust/server.md b/ai/skills/rust/server.md
index 6b5e1ada..585a6312 100644
--- a/ai/skills/rust/server.md
+++ b/ai/skills/rust/server.md
@@ -82,23 +82,31 @@ async fn main() -> miette::Result<()> {
 
 async fn shutdown_signal() {
     let ctrl_c = async {
-        signal::ctrl_c().await.expect("install ctrl-c handler");
+        if let Err(err) = signal::ctrl_c().await {
+            tracing::error!(%err, "failed to install SIGINT handler");
+            std::future::pending::<()>().await;
+        }
     };
 
     #[cfg(unix)]
     let terminate = async {
-        signal::unix::signal(signal::unix::SignalKind::terminate())
-            .expect("install SIGTERM handler")
-            .recv()
-            .await;
+        match signal::unix::signal(signal::unix::SignalKind::terminate()) {
+            Ok(mut sigterm) => {
+                sigterm.recv().await;
+            }
+            Err(err) => {
+                tracing::error!(%err, "failed to install SIGTERM handler");
+                std::future::pending::<()>().await;
+            }
+        }
     };
 
     #[cfg(not(unix))]
     let terminate = std::future::pending::<()>();
 
     tokio::select! {
-        _ = ctrl_c => tracing::info!("received SIGINT, shutting down"),
-        _ = terminate => tracing::info!("received SIGTERM, shutting down"),
+        () = ctrl_c => tracing::info!("received SIGINT, shutting down"),
+        () = terminate => tracing::info!("received SIGTERM, shutting down"),
     }
 }
 ```
@@ -106,6 +114,9 @@ async fn shutdown_signal() {
 Notes:
 
 - `shutdown_signal()` returns once either SIGINT or SIGTERM fires.
+  A handler that fails to install logs the error and parks forever
+  rather than panicking, so the other signal still works and the
+  template passes the `expect_used` lint under `-D warnings`.
   `axum::serve(...).with_graceful_shutdown(...)` stops accepting new
   connections and lets in-flight requests finish.
 - Distroless containers have no init process. Either rely on this