···11+# For available configuration options, see:
22+# https://github.com/standardrb/standard
33+ruby_version: 4.1
+150
AGENTS.md
···11+# AGENTS.md
22+33+## Repository summary
44+- Ruby gem that provides a BlueFactory feed implementation.
55+- Main entrypoint: `lib/bskypostroulettefeed.rb`.
66+- Feed definition and server configuration live in `lib/bskypostroulettefeed/feed/`.
77+- Tests use Minitest in `test/`.
88+- Linting/formatting uses StandardRB via `standard` gem.
99+- Build/release tasks come from `bundler/gem_tasks`.
1010+1111+## Setup
1212+- Install dependencies: `bin/setup` (runs `bundle install`).
1313+- Use `bundle exec` for all Ruby tooling.
1414+- Ruby version must satisfy the gemspec requirement (`>= 3.2.0`).
1515+1616+## Common commands
1717+- Default checks: `bundle exec rake` (runs tests + StandardRB).
1818+- Tests (all): `bundle exec rake test`.
1919+- Tests (single file): `bundle exec rake test TEST=test/test_bskypostroulettefeed.rb`.
2020+- Tests (single test name): `bundle exec ruby -Itest test/test_bskypostroulettefeed.rb -n test_name`.
2121+- Lint/format: `bundle exec standardrb`.
2222+- Lint via rake: `bundle exec rake standard`.
2323+- Build gem: `bundle exec rake build`.
2424+- Install gem locally: `bundle exec rake install`.
2525+- Interactive console: `bin/console`.
2626+- Run feed server (local dev): `bin/dev-feed` (requires env vars).
2727+2828+## Environment variables
2929+- `PUBLISHER_DID`: DID used by BlueFactory publisher.
3030+- `HOSTNAME`: hostname for feed endpoints.
3131+- Configure via shell env before running `bin/dev-feed`.
3232+3333+## Code layout
3434+- `lib/bskypostroulettefeed.rb` defines the gem module.
3535+- `lib/bskypostroulettefeed/version.rb` stores `VERSION`.
3636+- `lib/bskypostroulettefeed/feed/` contains feed config and implementation.
3737+- `sig/` stores RBS type signatures.
3838+- `test/` contains Minitest suites and `test_helper`.
3939+4040+## Formatting and linting (StandardRB)
4141+- Use StandardRB conventions; do not manually fight the formatter.
4242+- Run `bundle exec standardrb` after edits that touch Ruby files.
4343+- Prefer StandardRB defaults (2-space indent, no semicolons).
4444+- Let StandardRB manage trailing commas and hash formatting.
4545+4646+## Requires and file loading
4747+- Use `# frozen_string_literal: true` at top of Ruby files.
4848+- Order requires as: stdlib, gem dependencies, then `require_relative`.
4949+- Prefer `require_relative` for internal library files.
5050+- Keep require blocks minimal and scoped to file needs.
5151+5252+## Naming conventions
5353+- Modules/classes: CamelCase (`PostRouletteFeed`, `Bskypostroulettefeed`).
5454+- Methods/variables: snake_case.
5555+- Constants: SCREAMING_SNAKE_CASE.
5656+- Files: snake_case, mirroring module paths.
5757+- Test classes: `Test*` subclasses of `Minitest::Test`.
5858+- Test methods: `test_*` names.
5959+6060+## Types and signatures
6161+- Public API changes should be reflected in `sig/*.rbs`.
6262+- Keep RBS modules/classes aligned with Ruby implementation.
6363+- Use `String`, `Integer`, `Array[String]` style RBS types.
6464+- If adding new public classes, add matching RBS stubs.
6565+6666+## Error handling
6767+- Define custom errors under `Bskypostroulettefeed::Error` when needed.
6868+- Raise specific errors rather than generic `StandardError`.
6969+- Avoid rescuing broad exceptions unless you re-raise with context.
7070+- Include actionable messages for runtime errors.
7171+7272+## Configuration and environment
7373+- Use `ENV.fetch` when a variable is required.
7474+- Use `ENV["NAME"]` when optional.
7575+- Keep configuration centralized in `lib/bskypostroulettefeed/feed/config.rb`.
7676+7777+## Feed implementation guidance
7878+- `Feed#get_posts` should return a hash with `posts:` array.
7979+- `display_name` and `description` should return plain strings.
8080+- Keep network calls isolated; prefer small helper methods.
8181+8282+## Testing style
8383+- Require `test_helper` at the top of test files.
8484+- Use `refute_nil`, `assert`, `assert_equal`, etc. from Minitest.
8585+- Prefer small, focused tests over large integration cases.
8686+- Use fixture helpers in `test/` if new ones are introduced.
8787+8888+## Dependencies
8989+- Add runtime dependencies in `bskypostroulettefeed.gemspec`.
9090+- Add dev/test dependencies in `Gemfile`.
9191+- Prefer adding only what is needed to keep the gem slim.
9292+9393+## Documentation updates
9494+- Update `README.md` when public usage or CLI behavior changes.
9595+- Update `CHANGELOG.md` for noteworthy changes.
9696+9797+## Git and release notes
9898+- Use `bundle exec rake build` to verify packaging.
9999+- Avoid running `bundle exec rake release` unless instructed.
100100+- Do not update gemspec metadata unless asked.
101101+102102+## Editor and agent rules
103103+- No `.cursor/rules/`, `.cursorrules`, or Copilot instructions found.
104104+- If such files are added later, follow them for their scope.
105105+106106+## Writing new files
107107+- Match existing folder structure (`lib/`, `test/`, `sig/`).
108108+- Keep file names aligned with module names.
109109+110110+## Performance and clarity
111111+- Favor clear, readable Ruby over micro-optimizations.
112112+- Keep methods short and single-purpose.
113113+- Extract helpers when logic grows beyond a few lines.
114114+115115+## Security and secrets
116116+- Never commit API keys or tokens.
117117+- Use environment variables or local config files for secrets.
118118+119119+## Optional tooling (if needed)
120120+- Use `bundle exec rake -T` to list available rake tasks.
121121+- Use `bundle exec irb -r ./lib/bskypostroulettefeed` for quick REPL.
122122+123123+## Quick troubleshooting
124124+- Run `bundle exec rake` if unsure which checks to run.
125125+- If StandardRB fails, fix offenses then re-run.
126126+- If tests fail, run single tests with `-n` for isolation.
127127+128128+## Conventions to keep
129129+- Maintain `# frozen_string_literal: true` in Ruby source.
130130+- Keep `require_relative` paths consistent and minimal.
131131+- Keep module nesting aligned with folder structure.
132132+- Avoid monkey-patching global classes unless requested.
133133+134134+## Versioning
135135+- Update `lib/bskypostroulettefeed/version.rb` for releases.
136136+- Keep version as a semantic version string.
137137+138138+## BlueFactory specifics
139139+- `BlueFactory.add_feed` is configured in `feed/config.rb`.
140140+- `BlueFactory::Server.run!` is used in `bin/dev-feed`.
141141+- Keep feed registration centralized in config.
142142+143143+## New tests checklist
144144+- Add tests under `test/` and name files `test_*.rb`.
145145+- Ensure new tests run under `bundle exec rake test`.
146146+147147+## Summary for agents
148148+- Use StandardRB and Minitest.
149149+- Use `bundle exec` and update RBS with API changes.
150150+- Ask before running release-related commands.
···11+# Code of Conduct
22+33+"bskypostroulettefeed" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
44+55+* Participants will be tolerant of opposing views.
66+* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
77+* When interpreting the words and actions of others, participants should always assume good intentions.
88+* Behaviour which can be reasonably considered harassment will not be tolerated.
99+1010+If you have any concerns about behaviour within this project, please contact us at ["me@ansxor.ca"](mailto:"me@ansxor.ca").
···11+# Bskypostroulettefeed
22+33+TODO: Delete this and the text below, and describe your gem
44+55+Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bskypostroulettefeed`. To experiment with that code, run `bin/console` for an interactive prompt.
66+77+## Installation
88+99+TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
1010+1111+Install the gem and add to the application's Gemfile by executing:
1212+1313+```bash
1414+bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
1515+```
1616+1717+If bundler is not being used to manage dependencies, install the gem by executing:
1818+1919+```bash
2020+gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
2121+```
2222+2323+## Usage
2424+2525+TODO: Write usage instructions here
2626+2727+## Development
2828+2929+After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
3030+3131+To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
3232+3333+## Contributing
3434+3535+Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bskypostroulettefeed. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/bskypostroulettefeed/blob/master/CODE_OF_CONDUCT.md).
3636+3737+## Code of Conduct
3838+3939+Everyone interacting in the Bskypostroulettefeed project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/bskypostroulettefeed/blob/master/CODE_OF_CONDUCT.md).
4040+4141+## Relevant Libraries
4242+- https://ruby.sdk.blue/blue_factory/
4343+- https://ruby.sdk.blue/skyfall/
+38
Rakefile
···11+# frozen_string_literal: true
22+33+require "bundler/gem_tasks"
44+require "dotenv/load"
55+require "minitest/test_task"
66+require "sequel"
77+88+Minitest::TestTask.create
99+1010+require "blue_factory/rake"
1111+require "standard/rake"
1212+require_relative "lib/feed/config"
1313+require_relative "lib/firehose/firehose"
1414+1515+namespace :db do
1616+ desc "Run Sequel migrations"
1717+ task :migrate do
1818+ db = nil
1919+ database_url = ENV.fetch("DATABASE_URL")
2020+ db = Sequel.connect(database_url)
2121+ Sequel.extension :migration
2222+ Sequel::Migrator.run(db, "db/migrate")
2323+ ensure
2424+ db&.disconnect
2525+ end
2626+end
2727+2828+namespace :dev do
2929+ task :feed do
3030+ BlueFactory::Server.run!
3131+ end
3232+ task :firehose do
3333+ firehose = PostRouletteFeed::Firehose.new ENV.fetch("REDIS_URL")
3434+ firehose.run
3535+ end
3636+end
3737+3838+task default: %i[test standard]
+11
bin/console
···11+#!/usr/bin/env ruby
22+# frozen_string_literal: true
33+44+require "bundler/setup"
55+require "bskypostroulettefeed"
66+77+# You can add fixtures and/or initialization code here to make experimenting
88+# with your gem easier. You can also use a different console, if you like.
99+1010+require "irb"
1111+IRB.start(__FILE__)
+8
bin/setup
···11+#!/usr/bin/env bash
22+set -euo pipefail
33+IFS=$'\n\t'
44+set -vx
55+66+bundle install
77+88+# Do any other automated setup that you need to do here
+39
bskypostroulettefeed.gemspec
···11+# frozen_string_literal: true
22+33+require_relative "lib/bskypostroulettefeed/version"
44+55+Gem::Specification.new do |spec|
66+ spec.name = "bskypostroulettefeed"
77+ spec.version = Bskypostroulettefeed::VERSION
88+ spec.authors = ["ansxor"]
99+ spec.email = ["me@ansxor.ca"]
1010+1111+ spec.summary = "TODO: Write a short summary, because RubyGems requires one."
1212+ spec.description = "TODO: Write a longer description or delete this line."
1313+ spec.homepage = "TODO: Put your gem's website or public repo URL here."
1414+ spec.required_ruby_version = ">= 3.2.0"
1515+1616+ spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
1717+ spec.metadata["homepage_uri"] = spec.homepage
1818+ spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
1919+ spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
2020+2121+ # Specify which files should be added to the gem when it is released.
2222+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
2323+ gemspec = File.basename(__FILE__)
2424+ spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
2525+ ls.readlines("\x0", chomp: true).reject do |f|
2626+ (f == gemspec) ||
2727+ f.start_with?(*%w[bin/ Gemfile .gitignore test/ .github/ .standard.yml])
2828+ end
2929+ end
3030+ spec.bindir = "exe"
3131+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
3232+ spec.require_paths = ["lib"]
3333+3434+ # Uncomment to register a new dependency of your gem
3535+ # spec.add_dependency "example-gem", "~> 1.0"
3636+3737+ # For more information and examples about making a new gem, check out our
3838+ # guide at: https://bundler.io/guides/creating_gem.html
3939+end
+34
db/migrate/001_create_sample_records.rb
···11+# frozen_string_literal: true
22+33+Sequel.migration do
44+ change do
55+ extension :pg_enum
66+77+ create_enum(:backfill_status_enum, %w[unwatched queued backfilling active failed])
88+99+ create_table(:users) do
1010+ primary_key :id
1111+ String :did, null: false
1212+ backfill_status_enum :backfill_status, null: false, default: 'unwatched'
1313+ Boolean :is_feed_subscriber, null: false, default: false
1414+1515+ index :backfill_status
1616+ index :is_feed_subscriber
1717+ end
1818+1919+ create_join_table({follower_id: :users, followed_id: :users}, options: { name: 'user_relationships' }) do
2020+ index %i[follower_id followed_id], unique: true
2121+ index :followed_id
2222+ end
2323+2424+ create_table(:posts) do
2525+ primary_key :id
2626+ String :url, null: false
2727+ foreign_key :user_id, :users, null: false
2828+ Float :random_key, null: false
2929+3030+ index :user_id
3131+ index :random_key
3232+ end
3333+ end
3434+end
···11+module PostRouletteFeed
22+ class Feed
33+ def get_posts(params, context)
44+ p context.user
55+ { posts: ["at://did:plc:brka7yc4gssxdquiwpii22pr/app.bsky.feed.post/3mckyk6xwhc2y"] }
66+ end
77+88+ def display_name
99+ "Post Roulette"
1010+ end
1111+1212+ def description
1313+ "This is my testing feed for now"
1414+ end
1515+ end
1616+end
+23
lib/firehose/firehose.rb
···11+require 'skyfall'
22+require 'redis'
33+44+module PostRouletteFeed
55+ class Firehose
66+ def initialize redis_url
77+ @sky = Skyfall::Firehose.new("bsky.network", :subscribe_repos)
88+ @redis_url = redis_url
99+ end
1010+1111+ def run
1212+ redis = Redis.new(url: @redis_url)
1313+1414+ @sky.on_message do |msg|
1515+ if redis.exists? "watching:#{msg.did}"
1616+ p "watching", msg
1717+ end
1818+ end
1919+ @sky.connect
2020+ end
2121+ end
2222+end
2323+
+26
lib/workers/followgraphseeder.rb
···11+require 'sidekiq'
22+require 'minisky'
33+44+module PostRouletteFeed
55+ class FollowGraphSeeder
66+ include Sidekiq::Worker
77+88+ def perform(did)
99+ atproto = Minisky.new('public.api.bsky.app', nil)
1010+1111+ dids = []
1212+ cursor = nil
1313+1414+ while true do
1515+ json = atproto.get_request('app.bsky.graph.getFollows', {
1616+ actor: did,
1717+ limit: 100,
1818+ cursor: cursor
1919+ })
2020+ cursor = json["cursor"]
2121+ break if cursor.nil?
2222+ end
2323+ p "done"
2424+ end
2525+ end
2626+end
+4
sig/bskypostroulettefeed.rbs
···11+module Bskypostroulettefeed
22+ VERSION: String
33+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
44+end