Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude/rules/services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- "lib/services/**/*.rb"
---

# Service Conventions

- All HTTP calls use Ruby's native `Net::HTTP` — do not add external HTTP client gems
- Services are plain Ruby classes initialized with tokens/config, no framework coupling
- Constructor pattern: `initialize(token, debug: false, logger: nil)`
- API clients define a private `api_request` method that handles GET/POST, auth headers, and JSON parsing
- Error handling: check response status/`ok` field, log via `debug_log`, return empty/false on failure rather than raising
- SlackService auto-joins channels on `not_in_channel` errors — preserve this retry pattern
12 changes: 12 additions & 0 deletions .claude/rules/slack-modals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
paths:
- "lib/helpers/**/*.rb"
---

# Slack Modal Conventions

- Modals use Slack Block Kit format — return Ruby hashes that serialize to JSON
- Two modal types: `global_shortcut_modal` (needs both thread URL and issue URL inputs) and `message_shortcut_modal` (only needs issue URL, thread context passed via `private_metadata`)
- `private_metadata` carries `channel_id` and `thread_ts` as JSON between shortcut trigger and submission
- Callback IDs follow the pattern `gh_comment_modal_<type>` — these are matched in `app.rb` to route submissions
- Use `plain_text_input` elements with `block_id`/`action_id` pairs for form fields
15 changes: 15 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
paths:
- "test/**/*.rb"
---

# Testing Rules

- Use `Minitest::Spec` style (`describe`/`it` blocks)
- All external HTTP calls must be stubbed with WebMock — `WebMock.disable_net_connect!` is enforced globally
- Use the shared fixtures and stub helpers from `test/test_helper.rb`:
- Fixtures: `slack_message`, `slack_thread_response`, `slack_user_response`, `github_comment_response`, `slack_modal_payload`
- Stubs: `stub_slack_conversations_replies`, `stub_slack_users_info`, `stub_slack_chat_post_message`, `stub_github_create_comment`
- Integration tests use `Rack::Test` — call endpoints via `get`, `post` etc. and the `app` method returns `Sinatra::Application`
- `setup` calls `WebMock.reset!` automatically — no need to repeat it
- Test env vars (`SLACK_BOT_TOKEN`, `GITHUB_TOKEN`) are set in `test_helper.rb`
13 changes: 13 additions & 0 deletions .claude/skills/ci/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: ci
description: Run the full CI suite (syntax + rubocop + tests) and report results
allowed-tools: Bash(bundle exec *)
---

Run the full CI check suite:

```bash
bundle exec rake ci
```

Report the results clearly. If any step fails, identify which step failed and show the relevant error output.
19 changes: 19 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: release
description: Walk through the release process - preview changelog, bump version, create PR
disable-model-invocation: true
argument-hint: "[major|minor|patch]"
---

Create a release for this project. The bump type is: $ARGUMENTS (default to what `rake release:preview` suggests if not specified).

Steps:

1. Run `bundle exec rake release:preview` to show the current version, suggested bump type, and changelog preview
2. Confirm the bump type with the user before proceeding
3. Ensure the working directory is clean (`git status`)
4. Run `bundle exec rake ci` to verify all checks pass
5. Run `bundle exec rake release:$ARGUMENTS` to create the release (this bumps version, updates CHANGELOG.md, and creates a git tag)
6. Show the user the final commands to push: `git push origin main && git push origin <tag>`

Do NOT push automatically — let the user decide when to push.
33 changes: 33 additions & 0 deletions .claude/skills/review-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: review-pr
description: Review a pull request for code style, architecture, and correctness
disable-model-invocation: true
context: fork
agent: Explore
argument-hint: "[PR number or URL]"
allowed-tools: Bash(gh *)
---

Review pull request $ARGUMENTS against this project's conventions.

## Gather context

- PR diff: !`gh pr view $ARGUMENTS --json additions,deletions,changedFiles`
- PR details: !`gh pr view $ARGUMENTS`
- Full diff: !`gh pr diff $ARGUMENTS`

## Review checklist

1. **Code style**: RuboCop compliance (120-char lines, 20-line methods, single quotes)
2. **Architecture**: Services stay in `lib/services/`, helpers in `lib/helpers/`. Services use Net::HTTP, not external HTTP gems
3. **Testing**: New functionality has corresponding tests. All HTTP calls are stubbed with WebMock. Tests use shared fixtures from `test_helper.rb`
4. **Commit messages**: Follow conventional commits format (`feat:`, `fix:`, `docs:`, etc.)
5. **Error handling**: API errors handled gracefully, debug logging via `debug_log`

## Output

Provide a structured review with:
- Summary of changes
- Issues found (if any), with file and line references
- Suggestions for improvement
- Overall assessment (approve / request changes)
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
ruby-version: ['3.1', '3.2', '3.3']
ruby-version: ['3.2', '3.3', '3.4', '4.0']

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins:
- rubocop-rake

AllCops:
TargetRubyVersion: 3.1
TargetRubyVersion: 3.2
NewCops: enable
Exclude:
- "vendor/**/*"
Expand Down
64 changes: 64 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Slack-github-threads is a Ruby Sinatra app that exports Slack thread conversations as comments on GitHub issues. Users trigger it via a `/ghcomment` slash command or Slack shortcuts (global and message).

## Common Commands

```bash
bundle install # Install dependencies
bundle exec rake test # Run all tests
bundle exec rake test_services # Run service unit tests only
bundle exec rake test_app # Run integration tests only
bundle exec rake ci # Full CI: syntax + rubocop + tests
bundle exec rake rubocop # Lint only
bundle exec rake lint # Syntax check + rubocop
bundle exec rake server # Start dev server
DEBUG=true bundle exec ruby app.rb # Run with debug logging
```

To run a single test file: `bundle exec ruby test/services/test_text_processor.rb`

## Architecture

```
Slack (slash command / shortcut)
→ app.rb (Sinatra routing, 3 endpoints: GET /up, POST /ghcomment, POST /shortcut)
→ CommentService (orchestration)
├→ SlackService (fetch thread via conversations.replies, resolve user mentions)
├→ TextProcessor (format messages: HTML entity decoding, @mention replacement)
└→ GitHubService (POST comment to issue via REST API)
```

- **app.rb** — Entry point. Routes requests, validates params, delegates to CommentService.
- **lib/services/comment_service.rb** — Orchestrates the flow: fetch thread → format → post to GitHub → reply in Slack.
- **lib/services/slack_service.rb** — Slack API client (Net::HTTP). Auto-joins channels if bot isn't a member.
- **lib/services/github_service.rb** — GitHub API client (Net::HTTP). Parses issue URLs to extract org/repo/number.
- **lib/services/text_processor.rb** — Converts Slack message formatting to GitHub-compatible markdown.
- **lib/helpers/modal_builder.rb** — Constructs Slack Block Kit modals for shortcut flows.
- **lib/version_helper.rb** — Semantic versioning and changelog generation from conventional commits.

All API calls use Ruby's native `Net::HTTP` — no external HTTP client gems.

## Testing

- Framework: Minitest with `Minitest::Spec` style
- HTTP mocking: WebMock (all external calls must be stubbed)
- `test/test_helper.rb` provides shared fixtures (`slack_message`, `slack_thread_response`) and stub helpers (`stub_slack_conversations_replies`, `stub_github_create_comment`)
- Integration tests use `Rack::Test` against the Sinatra app

## Environment Variables

Required: `SLACK_BOT_TOKEN` (xoxb_*), `GITHUB_TOKEN` (ghp_*)
Optional: `DEBUG` (true/false), `RACK_ENV`, `PORT` (default 3000)

## Code Style

RuboCop enforced. Key rules: 120-char line limit, 20-line method limit, single quotes preferred. Uses rubocop-minitest and rubocop-rake plugins.

## Commit Conventions

Uses conventional commits: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`, `perf:`, `style:`. Breaking changes use `feat!:` or `BREAKING CHANGE:` footer. These drive automatic version bumping (major/minor/patch).
25 changes: 14 additions & 11 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,30 @@ GEM
rexml
daemons (1.4.1)
dotenv (3.1.8)
drb (2.2.3)
eventmachine (1.2.7)
hashdiff (1.2.0)
json (2.13.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
minitest (5.25.5)
mustermann (3.0.3)
minitest (6.0.2)
drb (~> 2.0)
prism (~> 1.5)
mustermann (3.0.4)
ruby2_keywords (~> 0.0.1)
nio4r (2.7.4)
parallel (1.27.0)
parser (3.3.9.0)
ast (~> 2.4.1)
racc
prism (1.4.0)
prism (1.9.0)
public_suffix (6.0.2)
puma (7.0.1)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.16)
rack-protection (4.1.1)
rack (3.2.5)
rack-protection (4.2.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
Expand All @@ -43,7 +46,7 @@ GEM
rainbow (3.1.1)
rake (13.3.0)
regexp_parser (2.10.0)
rexml (3.4.1)
rexml (3.4.4)
rubocop (1.79.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
Expand All @@ -67,22 +70,22 @@ GEM
rubocop (>= 1.72.1)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
sinatra (4.1.1)
sinatra (4.2.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1)
rack-protection (= 4.2.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
thin (2.0.1)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
logger
rack (>= 1, < 4)
tilt (2.6.1)
tilt (2.7.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-emoji (4.2.0)
webmock (3.25.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand All @@ -106,4 +109,4 @@ DEPENDENCIES
webmock

BUNDLED WITH
2.6.8
4.0.3
Loading