Skip to content

Commit cb01262

Browse files
authored
Merge pull request #2 from AxiumFoundry/add-claude-md
Add CLAUDE.md files for Claude Code context
2 parents 9972f50 + aa40610 commit cb01262

5 files changed

Lines changed: 189 additions & 0 deletions

File tree

‎CLAUDE.md‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CLAUDE.md
2+
3+
LlmClassifier - Ruby gem for building LLM-powered classifiers with a clean DSL. Supports multiple LLM backends (ruby_llm, OpenAI, Anthropic) and optional Rails integration.
4+
5+
- Ruby >= 3.2, RSpec, RuboCop, Zeitwerk autoloading
6+
- No Rails dependency in core; Rails integration is opt-in via `lib/llm_classifier/rails/`
7+
- CI tests against Ruby 3.4 and 4.0
8+
9+
## Development with Docker
10+
11+
Ruby is not installed on the host. Use Docker to run tests and linting:
12+
13+
```bash
14+
# Run tests and rubocop (Ruby 3.4)
15+
docker.exe run --rm -v "$(wslpath -w "$(pwd)"):/app" -w /app ruby:3.4-slim \
16+
bash -c "apt-get update -qq && apt-get install -y -qq build-essential git 2>/dev/null && \
17+
gem install bundler --no-document && bundle install --quiet && \
18+
bundle exec rspec && bundle exec rubocop"
19+
20+
# Rubocop only
21+
docker.exe run --rm -v "$(wslpath -w "$(pwd)"):/app" -w /app ruby:3.4-slim \
22+
bash -c "apt-get update -qq && apt-get install -y -qq build-essential git 2>/dev/null && \
23+
gem install bundler --no-document && bundle install --quiet && \
24+
bundle exec rubocop"
25+
26+
# Single spec file
27+
docker.exe run --rm -v "$(wslpath -w "$(pwd)"):/app" -w /app ruby:3.4-slim \
28+
bash -c "apt-get update -qq && apt-get install -y -qq build-essential git 2>/dev/null && \
29+
gem install bundler --no-document && bundle install --quiet && \
30+
bundle exec rspec spec/llm_classifier/classifier_spec.rb"
31+
```
32+
33+
Docker Desktop must be running on Windows. The `docker.exe` command is used because Docker runs via WSL2 integration. The `-v` flag bind-mounts the project so edits on host are immediately visible.
34+
35+
A `.devcontainer/` setup also exists for VS Code Dev Containers.
36+
37+
## Quick Commands (inside Docker)
38+
39+
```bash
40+
bundle exec rspec # all tests
41+
bundle exec rspec spec/llm_classifier/classifier_spec.rb # single file
42+
bundle exec rubocop # all files
43+
bundle exec rubocop -a # auto-correct
44+
gem build llm_classifier.gemspec # build gem
45+
```
46+
47+
## Project Structure
48+
49+
All sibling projects are located in `/home/axium/projects/`. The `prospector` gem depends on `llm_classifier`.
50+
51+
## Code Standards
52+
53+
- Double-quoted strings (enforced by RuboCop)
54+
- Max line length: 120 characters
55+
- Max method length: 20 lines
56+
- RSpec example max: 15 lines, max 6 expectations per example
57+
- `Style/HashExcept` disabled (requires ActiveSupport)
58+
- `Metrics/ClassLength` exempted for `classifier.rb` and `content_fetchers/web.rb`
59+
60+
## Git Workflow
61+
62+
- Never push directly to main. Always create a feature branch and PR.
63+
- Run the full test suite and rubocop before creating a PR.
64+
- Version bumps in `lib/llm_classifier/version.rb` go in the feature PR, not separately.
65+
66+
## Key Classes
67+
68+
- `LlmClassifier::Classifier` - Core DSL and classification pipeline
69+
- `LlmClassifier::Result` - Value object returned from every classification
70+
- `LlmClassifier::Knowledge` - Domain knowledge DSL container (`method_missing`-based)
71+
- `LlmClassifier::Configuration` - Global config (adapter, model, API keys)
72+
- `LlmClassifier::Adapters::Base` - Abstract adapter interface
73+
- `LlmClassifier::ContentFetchers::Web` - HTTP fetcher with SSRF protection
74+
- `LlmClassifier::Rails::Concerns::Classifiable` - ActiveRecord integration
75+
76+
## Component Documentation
77+
78+
- [lib/llm_classifier/adapters/CLAUDE.md](lib/llm_classifier/adapters/CLAUDE.md) - LLM adapter contract and implementations
79+
- [lib/llm_classifier/content_fetchers/CLAUDE.md](lib/llm_classifier/content_fetchers/CLAUDE.md) - Content fetchers and SSRF protection
80+
- [lib/llm_classifier/rails/CLAUDE.md](lib/llm_classifier/rails/CLAUDE.md) - Rails integration (Zeitwerk-excluded)
81+
- [spec/CLAUDE.md](spec/CLAUDE.md) - Testing conventions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Adapters
2+
3+
LLM provider adapters. All inherit from `Adapters::Base` and implement `#chat(model:, system_prompt:, user_prompt:)`.
4+
5+
## Inventory
6+
7+
- `Base` - Abstract interface. Provides `#config` helper for accessing `LlmClassifier.configuration`
8+
- `RubyLlm` - Delegates to the `ruby_llm` gem. Returns a Hash with `:content`, `:input_tokens`, `:output_tokens`
9+
- `OpenAI` - Direct `Net::HTTP` POST to OpenAI API. Returns a String
10+
- `Anthropic` - Direct `Net::HTTP` POST to Anthropic API. Returns a String
11+
12+
## Conventions
13+
14+
- `#chat` returns either a String (raw content) or a Hash with `:content` plus optional token metadata
15+
- `Classifier#extract_response_data` handles both return types, so new adapters can use either
16+
- API keys come from `LlmClassifier.configuration`, not hardcoded or passed as arguments
17+
- Custom adapters can be a class instance passed directly to `config.adapter`
18+
19+
## Related
20+
21+
- [../content_fetchers/CLAUDE.md](../content_fetchers/CLAUDE.md) - Content fetchers
22+
- [../../spec/CLAUDE.md](../../spec/CLAUDE.md) - Testing conventions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Content Fetchers
2+
3+
Utilities for fetching external content to use as classification input. Not wired into `Classifier` automatically -- callers fetch content and pass it in.
4+
5+
## Inventory
6+
7+
- `Base` - Abstract interface. Subclasses implement `#fetch(source)`
8+
- `Web` - HTTP fetcher with SSRF protection, redirect following, and HTML text extraction
9+
- `Null` - No-op fetcher, always returns `nil`
10+
11+
## SSRF Protection (`Web`)
12+
13+
- Validates resolved IPs against private/loopback CIDR ranges before connecting
14+
- Follows up to 3 redirects, re-validating each redirect target
15+
- `normalize_redirect_url` handles relative and absolute redirect URLs
16+
- Uses `nil? || empty?` guards (not ActiveSupport `.blank?`) to avoid the dependency
17+
18+
## HTML Processing (`Web`)
19+
20+
- Nokogiri is lazily loaded (`require "nokogiri"` inside the method) since it's an optional dependency
21+
- Strips `<script>`, `<style>`, `<nav>`, `<footer>`, `<header>` elements
22+
- Truncates extracted text to 2000 characters
23+
24+
## Related
25+
26+
- [../adapters/CLAUDE.md](../adapters/CLAUDE.md) - LLM adapters
27+
- [../../spec/CLAUDE.md](../../spec/CLAUDE.md) - Testing conventions

‎lib/llm_classifier/rails/CLAUDE.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Rails Integration
2+
3+
This entire subtree is excluded from Zeitwerk autoloading (`loader.ignore` in `lib/llm_classifier.rb`) and loaded manually only when `Rails::Railtie` is defined. This keeps Rails as an optional dependency.
4+
5+
## Inventory
6+
7+
- `Railtie` - Sets `Rails.logger` as the default LlmClassifier logger
8+
- `Concerns::Classifiable` - ActiveRecord concern adding a `classifies` macro
9+
- `Generators::InstallGenerator` - `rails g llm_classifier:install` scaffolds an initializer
10+
- `Generators::ClassifierGenerator` - `rails g llm_classifier:classifier Name cat1 cat2` scaffolds a classifier and spec
11+
12+
## Classifiable Concern
13+
14+
The `classifies` macro defines three instance methods per classification:
15+
16+
- `classify_<attr>!` - Runs classification and stores the result
17+
- `<attr>_category` / `<attr>_categories` - Reads stored category data
18+
- `<attr>_classification` - Returns the full stored classification hash
19+
20+
Results are written into a JSONB column (via `store_in:`) or a transient instance variable if no column is specified.
21+
22+
## Related
23+
24+
- [../adapters/CLAUDE.md](../adapters/CLAUDE.md) - LLM adapters
25+
- [../content_fetchers/CLAUDE.md](../content_fetchers/CLAUDE.md) - Content fetchers

‎spec/CLAUDE.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Testing Conventions
2+
3+
RSpec 3.x with WebMock and VCR.
4+
5+
## Setup
6+
7+
- All external HTTP is blocked by WebMock in tests
8+
- VCR cassettes go in `spec/fixtures/vcr_cassettes/` (tag examples with `:vcr` for auto-recording)
9+
- API keys are filtered from cassettes (`<OPENAI_API_KEY>`, `<ANTHROPIC_API_KEY>`)
10+
- `LlmClassifier.reset_configuration!` runs before every example to isolate global state
11+
- `verify_partial_doubles: true` enforces strict mocking
12+
13+
## Running Tests
14+
15+
```bash
16+
bundle exec rspec # all tests
17+
bundle exec rspec spec/llm_classifier/classifier_spec.rb # single file
18+
bundle exec rspec --only-failures # re-run failures
19+
```
20+
21+
## Spec Files
22+
23+
- `llm_classifier_spec.rb` - Module-level configure/reset
24+
- `llm_classifier/classifier_spec.rb` - DSL attributes, `.classify` pipeline, token data, code-fence stripping, callbacks
25+
- `llm_classifier/result_spec.rb` - Result factory methods, `#to_h`, `#multi_label?`, token fields
26+
- `llm_classifier/knowledge_spec.rb` - Dynamic storage, `#to_prompt` formatting
27+
28+
No specs exist yet for: adapters, content fetchers, the Rails concern, or generators.
29+
30+
## Related
31+
32+
- [../lib/llm_classifier/adapters/CLAUDE.md](../lib/llm_classifier/adapters/CLAUDE.md) - LLM adapters
33+
- [../lib/llm_classifier/content_fetchers/CLAUDE.md](../lib/llm_classifier/content_fetchers/CLAUDE.md) - Content fetchers
34+
- [../lib/llm_classifier/rails/CLAUDE.md](../lib/llm_classifier/rails/CLAUDE.md) - Rails integration

0 commit comments

Comments
 (0)