RepoPilotOpen in app →

krisleech/wisper

A micro library providing Ruby objects with Publish-Subscribe capabilities

Concerns

Stale and unlicensed — last commit 2y ago

worst of 4 axes
Use as dependencyConcerns

no license — legally unclear; last commit was 2y ago

Fork & modifyConcerns

no license — can't legally use code

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isConcerns

no license — can't legally use code; last commit was 2y ago

  • 27+ active contributors
  • CI configured
  • Tests present
Show 3 more →
  • Stale — last commit 2y ago
  • Concentrated ownership — top contributor handles 62% of recent commits
  • No license — legally unclear to depend on
What would change the summary?
  • Use as dependency ConcernsMixed if: publish a permissive license (MIT, Apache-2.0, etc.)
  • Fork & modify ConcernsMixed if: add a LICENSE file
  • Deploy as-is ConcernsMixed if: add a LICENSE file

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Great to learn from" badge

Paste into your README — live-updates from the latest cached analysis.

RepoPilot: Great to learn from
[![RepoPilot: Great to learn from](https://repopilot.app/api/badge/krisleech/wisper?axis=learn)](https://repopilot.app/r/krisleech/wisper)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/krisleech/wisper on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: krisleech/wisper

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/krisleech/wisper shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

AVOID — Stale and unlicensed — last commit 2y ago

  • 27+ active contributors
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 2y ago
  • ⚠ Concentrated ownership — top contributor handles 62% of recent commits
  • ⚠ No license — legally unclear to depend on

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live krisleech/wisper repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/krisleech/wisper.

What it runs against: a local clone of krisleech/wisper — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in krisleech/wisper | Confirms the artifact applies here, not a fork | | 2 | Default branch master exists | Catches branch renames | | 3 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 4 | Last commit ≤ 662 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>krisleech/wisper</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of krisleech/wisper. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/krisleech/wisper.git
#   cd wisper
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of krisleech/wisper and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "krisleech/wisper(\\.git)?\\b" \\
  && ok "origin remote is krisleech/wisper" \\
  || miss "origin remote is not krisleech/wisper (artifact may be from a fork)"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "lib/wisper.rb" \\
  && ok "lib/wisper.rb" \\
  || miss "missing critical file: lib/wisper.rb"
test -f "lib/wisper/publisher.rb" \\
  && ok "lib/wisper/publisher.rb" \\
  || miss "missing critical file: lib/wisper/publisher.rb"
test -f "lib/wisper/registration/registration.rb" \\
  && ok "lib/wisper/registration/registration.rb" \\
  || miss "missing critical file: lib/wisper/registration/registration.rb"
test -f "lib/wisper/broadcasters/send_broadcaster.rb" \\
  && ok "lib/wisper/broadcasters/send_broadcaster.rb" \\
  || miss "missing critical file: lib/wisper/broadcasters/send_broadcaster.rb"
test -f "lib/wisper/global_listeners.rb" \\
  && ok "lib/wisper/global_listeners.rb" \\
  || miss "missing critical file: lib/wisper/global_listeners.rb"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 662 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~632d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/krisleech/wisper"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Wisper is a lightweight Ruby pub-sub library that decouples publishers from subscribers by providing the Wisper::Publisher mixin to enable objects to broadcast events and accept runtime subscriptions. It solves the problem of tight coupling in Rails apps by replacing callbacks and observers with explicit, context-aware event publishing—events like :cancel_order_successful can be broadcast to any registered listener (objects or blocks) without the publisher knowing about them. Simple flat architecture: core module Wisper::Publisher in lib/wisper/publisher.rb provides broadcast() and subscribe() methods; registration logic split into lib/wisper/registration/ with block.rb and object.rb handling two subscription types; broadcasters (sync send, logging) in lib/wisper/broadcasters/; global listeners and temporary listeners in separate files for scope isolation. Entry point is lib/wisper.rb.

👥Who it's for

Rails developers and Ruby application architects who need to decouple business logic from side effects (notifications, logging, async jobs) and want an alternative to ActiveRecord callbacks. Also useful for building hexagonal/ports-and-adapters architectures where events flow between domain objects without direct dependencies.

🌱Maturity & risk

Production-ready and stable. The gem is actively maintained with CI passing via GitHub Actions (.github/workflows/test.yml), has comprehensive specs under spec/, follows semantic versioning (currently v3.0+), and README includes real usage examples. However, recent commit frequency is unclear from the file listing alone, though the well-organized test structure and changelog suggest sustained care.

Low risk. Single-author gem (krisleech) with no visible dependency bloat—the core is ~50KB of pure Ruby in lib/wisper/. Main risk is maintainer bus factor (single maintainer evident from repo structure), but the codebase is stable and the problem domain is mature. No breaking changes are evident in recent CHANGELOG snippets provided.

Active areas of work

Unclear from file listing alone—no PR metadata or recent commit dates visible. The repo appears stable and mature rather than actively developing new features. README mentions WisperNext and Ma as recommended alternatives for greenfield projects, suggesting this is in maintenance mode.

🚀Get running

git clone https://github.com/krisleech/wisper.git
cd wisper
bundle install
bundle exec rspec

Daily commands: This is a library gem, not a runnable application. To see it in action: bundle exec rspec runs the full test suite (specs are in spec/lib/). For experimentation: bundle exec bin/console launches an IRB session with the gem loaded (see bin/console).

🗺️Map of the codebase

  • lib/wisper.rb — Main entry point that includes the Publisher module; every use of Wisper starts here.
  • lib/wisper/publisher.rb — Core publish-subscribe logic; defines how objects broadcast events and manage listeners.
  • lib/wisper/registration/registration.rb — Handles listener registration state and lifecycle; critical for understanding event routing.
  • lib/wisper/broadcasters/send_broadcaster.rb — Default broadcaster implementation that delivers events to registered listeners via method calls.
  • lib/wisper/global_listeners.rb — Manages global event listeners; essential for understanding application-wide pub-sub patterns.
  • lib/wisper/configuration.rb — Centralizes Wisper configuration including broadcaster selection; needed for customization.
  • spec/lib/integration_spec.rb — End-to-end integration tests demonstrating real-world Wisper usage patterns.

🧩Components & responsibilities

  • Publisher (Ruby Module) — Orchestrates event publishing; manages listener registrations and invokes broadcaster
    • Failure mode: If publish fails, exception bubbles to caller; listeners may be partially invoked
  • Broadcaster (Strategy pattern, Ruby send()) — Delivers published events to registered listeners via method dispatch or custom strategy
    • Failure mode: If listener method raises, exception propagates; other listeners still execute
  • Registration (Object/Block adapters) — Tracks listener subscription state and lifecycle (permanent or temporary)
    • Failure mode: If registration destroyed improperly, listeners may receive stale events
  • Global Listeners (Class-level state in Wisper module) — Singleton registry for application-wide event observers
    • Failure mode: Memory leaks if listeners not explicitly unsubscribed; affects all publishers
  • Configuration (Singleton pattern) — Centralizes broadcaster selection and other Wisper settings
    • Failure mode: If broadcaster is unconfigurable, cannot swap strategies at runtime

🔀Data flow

  • Application CodePublisher — Calls publish(:event_name, data) or subscribe(listener)
  • PublisherRegistration — Queries active registrations for a given publisher instance
  • PublisherGlobal Listeners — Queries global subscribers to include in broadcast list
  • PublisherBroadcaster — Passes listeners, event name, and args for delivery
  • BroadcasterListener — Invokes on_event_name(args) via send() or custom dispatch
  • ConfigurationBroadcaster — Selects which broadcaster strategy to use

🛠️How to make changes

Add a Custom Broadcaster

  1. Create a new broadcaster class in lib/wisper/broadcasters/ that responds to call(listeners, event, args) (lib/wisper/broadcasters/your_broadcaster.rb)
  2. Register your broadcaster in Wisper.configure block via Wisper::Configuration (lib/wisper/configuration.rb)
  3. Write integration tests for your broadcaster behavior (spec/lib/wisper/broadcasters/your_broadcaster_spec.rb)

Add Global Event Listeners

  1. Use Wisper.subscribe(listener_object) or Wisper.subscribe { |event, args| } in your app initialization (lib/wisper/global_listeners.rb)
  2. Listener will receive all events from all publishers; implement on_<event_name> methods or use block pattern (spec/lib/global_listeners_spec.rb)

Add Temporary Scoped Listeners

  1. Call publisher.subscribe(listener) or publisher.on(:event_name) { } within a scope (lib/wisper/publisher.rb)
  2. Listeners are automatically unsubscribed when the subscription block exits (lib/wisper/temporary_listeners.rb)
  3. Test scoped behavior with temporary listener tests (spec/lib/temporary_global_listeners_spec.rb)

Publish Custom Events

  1. Include Wisper::Publisher in your domain model class (lib/wisper/publisher.rb)
  2. Call publish(:event_name, arg1, arg2) or publish(:event_name) { } from instance methods (lib/wisper/publisher.rb)
  3. Listeners receive events via on_event_name(arg1, arg2) methods or callbacks (spec/lib/simple_example_spec.rb)

🔧Why these technologies

  • Pure Ruby (no external dependencies) — Keeps library lightweight, framework-agnostic, and suitable for microservices without bloat
  • Method dispatch via send() — Enables loose coupling between publishers and listeners; listeners receive events through convention (on_event_name methods)
  • Registration objects pattern — Provides clean abstraction for managing listener lifecycles (global, temporary, scoped)
  • Broadcaster strategy pattern — Allows pluggable delivery mechanisms (send, logging, async, custom) without changing core logic

⚖️Trade-offs already made

  • Synchronous default event delivery

    • Why: Maintains predictable flow and allows immediate feedback from listener actions
    • Consequence: Long-running listeners block the publisher; async delivery must be implemented via custom broadcaster
  • Convention over configuration (on_event_name methods)

    • Why: Reduces boilerplate and makes listener intent clear without explicit mapping
    • Consequence: Event names must follow Ruby method naming conventions; harder to discover available events
  • No event ordering guarantees

    • Why: Simplifies implementation and supports multiple broadcaster strategies
    • Consequence: Multi-listener scenarios must not depend on execution order; listeners are independent
  • Global listeners as application-wide concern

    • Why: Provides simple cross-cutting event observation without middleware overhead
    • Consequence: Global listeners increase implicit coupling across the application

🚫Non-goals (don't propose these)

  • Asynchronous event delivery (requires custom broadcaster implementation)
  • Event persistence or event sourcing (in-memory only)
  • Distributed pub-sub across processes (single Ruby process only)
  • Type safety or schema validation for events
  • Priority-based listener execution ordering
  • Dead letter queue or event retry logic

🪤Traps & gotchas

No hidden traps identified from the file structure provided. Configuration is straightforward in lib/wisper/configuration.rb. One minor gotcha: block subscriptions use .on() chaining (returns self), while object subscriptions use .subscribe() (different fluent interface), which could confuse newcomers but is documented in README. Event method names must match broadcast calls exactly (no automatic underscoring).

🏗️Architecture

💡Concepts to learn

  • Observer Pattern — Wisper is a Ruby implementation of the Observer pattern; understanding the intent (decoupling subjects from observers) clarifies why you'd use Wisper over Rails callbacks
  • Publish-Subscribe (Pub-Sub) — Core architectural pattern in Wisper; publishers emit events without knowing subscribers, enabling context-dependent wiring at runtime rather than compile-time
  • Module Mixins (Ruby) — Wisper uses include Wisper::Publisher (mixin) rather than inheritance to add pub-sub behavior; understanding Ruby's module system is essential to using and extending Wisper
  • Hexagonal Architecture (Ports & Adapters) — Wisper is explicitly designed for hexagonal architectures (mentioned in README) where domain logic broadcasts events that external concerns subscribe to; understanding this pattern clarifies Wisper's role
  • Strategy Pattern (Broadcasters) — Wisper's lib/wisper/broadcasters/ uses pluggable broadcast strategies (send vs. logger vs. async); understanding strategy pattern helps in extending with custom broadcasters
  • Double Dispatch (Dynamic Method Invocation)send_broadcaster.rb uses Ruby's send() to dynamically invoke listener methods by event name; understanding this technique is key to how Wisper routes events
  • Fluent Interface / Method Chaining — Block subscriptions return self to enable .on(:event1) { }.on(:event2) { } chaining; understanding fluent interfaces clarifies the API design
  • dry-rb/dry-events — Alternative Ruby pub-sub library with more formal event typing and validation; direct competitor solving the same decoupling problem
  • rails/rails — ActiveRecord callbacks and Observer pattern that Wisper explicitly replaces; understanding Rails' observer pattern helps motivate Wisper's design
  • hanami/hanami — Full-stack Ruby framework that uses pub-sub patterns similar to Wisper for event-driven architecture; common integration point
  • krisleech/wisper_next — Author's recommended successor for greenfield projects (mentioned in README); successor codebase with modernized patterns
  • arkency/aggregate_root — Ruby gem for DDD aggregates with event sourcing; often used alongside Wisper in domain-driven Ruby applications

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive async/await broadcaster implementation and tests

The repo mentions 'Publish events synchronously or asynchronously' in the README, but only has two broadcasters implemented (logger_broadcaster.rb and send_broadcaster.rb), both of which appear synchronous. There's no async broadcaster in lib/wisper/broadcasters/, and no corresponding async tests. This is a core feature mentioned in the marketing but apparently missing implementation.

  • [ ] Create lib/wisper/broadcasters/async_broadcaster.rb with async job queue support
  • [ ] Add spec/lib/wisper/broadcasters/async_broadcaster_spec.rb with tests for queuing and async execution
  • [ ] Update lib/wisper/configuration.rb to register the new async broadcaster
  • [ ] Add integration test in spec/lib/integration_spec.rb demonstrating async event publishing

Add error handling and resilience tests for broadcaster failures

There are no visible tests for broadcaster failure modes (e.g., what happens if a listener raises an exception, or a broadcaster fails). This is critical for production use in Hexagonal architectures. The spec files don't contain tests for exception handling across lib/wisper/registration/ modules or broadcaster chains.

  • [ ] Add spec/lib/wisper/registration/error_handling_spec.rb with tests for listener exceptions, broadcaster failures, and error propagation
  • [ ] Update lib/wisper/publisher.rb with error handling logic if missing, ensuring listeners don't break the broadcast chain
  • [ ] Add spec/lib/wisper/broadcasters/broadcaster_chain_resilience_spec.rb testing multiple broadcasters with mixed success/failure states
  • [ ] Document error handling behavior in CONTRIBUTING.md or README.md

Add conditional/filtered listener registration tests and implementation

The file structure shows lib/wisper/registration/registration.rb and lib/wisper/value_objects/prefix.rb, suggesting filtering capabilities. However, spec/lib/wisper/registrations/object_spec.rb is the only registration test file, and there's no visible test coverage for advanced filtering, conditional listeners, or prefix-based event filtering patterns that would be valuable for complex Hexagonal architectures.

  • [ ] Add spec/lib/wisper/registration/conditional_listener_spec.rb with tests for filtering listeners by event type, prefix, and custom predicates
  • [ ] Add spec/lib/wisper/value_objects/filter_spec.rb if filter logic doesn't exist, testing event filtering and matching
  • [ ] Add integration test in spec/lib/integration_spec.rb demonstrating conditional listener registration and selective event delivery
  • [ ] Document conditional listener patterns with examples in README.md

🌿Good first issues

  • Add spec coverage for edge cases in lib/wisper/value_objects/prefix.rb—currently it exists but spec/lib/wisper/value_objects/prefix_spec.rb may be incomplete; add tests for prefix collision handling and edge cases.
  • Implement a built-in async broadcaster (e.g., using Thread or Sidekiq integration example) in lib/wisper/broadcasters/async_broadcaster.rb—the library supports pluggable broadcasters but only has send_broadcaster.rb and logger_broadcaster.rb as examples, and README mentions 'synchronously or asynchronously' but only shows sync.
  • Expand README with a concrete Rails example showing how to use Wisper instead of after_save callbacks—current README shows domain objects but not Rails integration, which is the primary use case mentioned in the description.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 4569343 — Merge pull request #216 from amatsuda/exclude_specs_from_gem_package (krisleech)
  • 559940a — exclude_specs_from_gem_package (amatsuda)
  • 9cff687 — Merge pull request #214 from stgeneral/patch-1 (krisleech)
  • 1f0c23c — Update README.md - fix gem installation version (stgeneral)
  • 5be73c7 — v3.0.0 (krisleech)
  • a42e28d — resigns expired certificate (Kris Leech)
  • d60e23e — v3.0.0.rc1 (Kris Leech)
  • 0fb1e4d — Merge pull request #210 from maboelnour/patch-1 (krisleech)
  • 973a1b1 — Merge pull request #212 from kianmeng/fix-typos (krisleech)
  • 6931dee — Fix typos (kianmeng)

🔒Security observations

The Wisper library demonstrates a solid security posture as a lightweight pub-sub Ruby library. No critical vulnerabilities were identified in the provided file structure. The codebase appears well-organized with clear separation of concerns (broadcasters, registrations, value objects). Primary recommendations focus on: (1) verifying gem signing certificate management practices, (2) conducting full dependency vulnerability analysis using bundle audit, (3) ensuring CI/CD pipeline security configuration, and (4) completing documentation references. As a library focused on publish-subscribe patterns rather than data persistence or external integrations, traditional injection risks (SQLi, XSS) are minimal. The absence of visible secrets, credentials, or hardcoded sensitive data is positive.

  • Medium · Public Certificate File Present — gem-public_cert.pem. The file 'gem-public_cert.pem' is present in the repository root. While public certificates are not secrets by definition, their presence alongside gem signing practices could indicate certificate management practices that should be reviewed. If this is a public gem, ensure the private key is never committed. Fix: Verify that only the public certificate is committed and the private key is stored securely outside the repository. Consider documenting gem signing practices in CONTRIBUTING.md.
  • Low · Incomplete README URL — README.md. The README.md file contains an incomplete URL link to the Wiki (ends with 'https://github.com/krisleech/wisper'). While not a direct security issue, incomplete documentation can lead to confusion and potential misuse of the library. Fix: Complete the Wiki URL reference in the README to ensure users have access to full documentation.
  • Low · No Dependency File Content Provided — Gemfile, wisper.gemspec. The Gemfile and wisper.gemspec dependency declarations were not provided for analysis. Without reviewing the dependencies, it's impossible to assess if insecure or outdated gems are being used. Fix: Review all dependencies using 'bundle audit' to check for known vulnerabilities. Keep gems updated to the latest secure versions.
  • Low · Missing Security Headers in CI/CD Configuration — .github/workflows/test.yml. The GitHub Actions workflow file (.github/workflows/test.yml) was not provided for review. CI/CD pipelines can be vectors for supply chain attacks if not properly configured with security practices. Fix: Ensure the CI/CD pipeline includes: secret scanning, dependency vulnerability checks, SARIF uploads for GitHub security tab, and restricted permissions for workflow tokens.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.