njtransit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/commands/njtransit.md +196 -0
  3. data/.mcp.json.example +12 -0
  4. data/.mcp.json.sample +11 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +87 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +37 -0
  9. data/CLAUDE.md +159 -0
  10. data/CODE_OF_CONDUCT.md +84 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +148 -0
  13. data/Rakefile +12 -0
  14. data/docs/plans/2025-01-24-njtransit-gem-design.md +112 -0
  15. data/docs/plans/2026-01-24-bus-api-design.md +119 -0
  16. data/docs/plans/2026-01-24-gtfs-implementation.md +2216 -0
  17. data/docs/plans/2026-01-24-gtfs-loader-design.md +351 -0
  18. data/docs/superpowers/plans/2026-03-26-dev-infra-and-agent.md +480 -0
  19. data/lefthook.yml +17 -0
  20. data/lib/njtransit/client.rb +291 -0
  21. data/lib/njtransit/configuration.rb +49 -0
  22. data/lib/njtransit/error.rb +50 -0
  23. data/lib/njtransit/gtfs/database.rb +145 -0
  24. data/lib/njtransit/gtfs/importer.rb +124 -0
  25. data/lib/njtransit/gtfs/models/route.rb +59 -0
  26. data/lib/njtransit/gtfs/models/stop.rb +63 -0
  27. data/lib/njtransit/gtfs/queries/routes_between.rb +62 -0
  28. data/lib/njtransit/gtfs/queries/schedule.rb +75 -0
  29. data/lib/njtransit/gtfs.rb +119 -0
  30. data/lib/njtransit/railtie.rb +9 -0
  31. data/lib/njtransit/resources/base.rb +35 -0
  32. data/lib/njtransit/resources/bus/enrichment.rb +105 -0
  33. data/lib/njtransit/resources/bus.rb +95 -0
  34. data/lib/njtransit/resources/bus_gtfs.rb +34 -0
  35. data/lib/njtransit/resources/rail.rb +47 -0
  36. data/lib/njtransit/resources/rail_gtfs.rb +27 -0
  37. data/lib/njtransit/tasks.rb +74 -0
  38. data/lib/njtransit/version.rb +5 -0
  39. data/lib/njtransit.rb +40 -0
  40. data/sig/njtransit.rbs +4 -0
  41. metadata +177 -0
@@ -0,0 +1,480 @@
1
+ # NJTransit Gem: Dev Infra Alignment + Terminal Agent — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Align njtransit gem's dev infrastructure (linting, CI, hooks) with the tenor project's patterns, and create a Claude Code terminal agent that can answer real-time NJ Transit questions.
6
+
7
+ **Architecture:** Four independent workstreams: (1) RuboCop + Gemfile cleanup, (2) GitHub Actions CI upgrade, (3) lefthook alignment, (4) Claude Code slash command agent. The first three are infrastructure — no Ruby code changes. The fourth is a new `.claude/commands/njtransit.md` file that teaches Claude how to use the gem's API.
8
+
9
+ **Tech Stack:** RuboCop, RSpec, Lefthook, GitHub Actions, Claude Code custom commands
10
+
11
+ ---
12
+
13
+ ## File Structure
14
+
15
+ | File | Action | Purpose |
16
+ |------|--------|---------|
17
+ | `.rubocop.yml` | Modify | Tighten config, add rubocop-rspec |
18
+ | `Gemfile` | Modify | Add rubocop-rspec dev dependency |
19
+ | `.github/workflows/ci.yml` | Modify | Add RuboCop caching, update action versions, add dependabot |
20
+ | `.github/dependabot.yml` | Create | Weekly dependency updates (matches tenor) |
21
+ | `lefthook.yml` | Modify | Add `parallel: true` to pre-push (minor alignment) |
22
+ | `.claude/commands/njtransit.md` | Create | Claude Code slash command for NJ Transit queries |
23
+
24
+ ---
25
+
26
+ ### Task 1: RuboCop + Gemfile Alignment
27
+
28
+ **Files:**
29
+ - Modify: `Gemfile`
30
+ - Modify: `.rubocop.yml`
31
+
32
+ - [ ] **Step 1: Add rubocop-rspec to Gemfile**
33
+
34
+ In `Gemfile`, change:
35
+ ```ruby
36
+ gem "rubocop", "~> 1.21"
37
+ ```
38
+ to:
39
+ ```ruby
40
+ gem "rubocop", "~> 1.21"
41
+ gem "rubocop-rspec", "~> 3.0", require: false
42
+ ```
43
+
44
+ - [ ] **Step 2: Update .rubocop.yml**
45
+
46
+ Replace the full `.rubocop.yml` with:
47
+ ```yaml
48
+ require:
49
+ - rubocop-rspec
50
+
51
+ AllCops:
52
+ TargetRubyVersion: 3.2
53
+ NewCops: enable
54
+ SuggestExtensions: false
55
+
56
+ Style/StringLiterals:
57
+ EnforcedStyle: double_quotes
58
+
59
+ Style/StringLiteralsInInterpolation:
60
+ EnforcedStyle: double_quotes
61
+
62
+ Style/Documentation:
63
+ Enabled: false
64
+
65
+ Style/CommentedKeyword:
66
+ Enabled: false
67
+
68
+ Layout/LineLength:
69
+ Max: 120
70
+
71
+ Metrics/BlockLength:
72
+ Exclude:
73
+ - "spec/**/*"
74
+ - "*.gemspec"
75
+ - "lib/njtransit/tasks.rb"
76
+
77
+ # HTTP client code tends to have longer classes/methods
78
+ Metrics/ClassLength:
79
+ Exclude:
80
+ - "lib/njtransit/client.rb"
81
+ - "lib/njtransit/gtfs/database.rb"
82
+
83
+ Metrics/MethodLength:
84
+ Exclude:
85
+ - "lib/njtransit/client.rb"
86
+
87
+ Metrics/AbcSize:
88
+ Exclude:
89
+ - "lib/njtransit/client.rb"
90
+
91
+ Metrics/CyclomaticComplexity:
92
+ Exclude:
93
+ - "lib/njtransit/client.rb"
94
+
95
+ # GTFS database schema is inherently verbose
96
+ Metrics/ModuleLength:
97
+ Exclude:
98
+ - "lib/njtransit/gtfs/database.rb"
99
+ ```
100
+
101
+ Key changes: added `rubocop-rspec` require, reduced line length from 180 to 120.
102
+
103
+ - [ ] **Step 3: Run bundle install**
104
+
105
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle install`
106
+ Expected: Gemfile.lock updated with rubocop-rspec
107
+
108
+ - [ ] **Step 4: Run rubocop to check for new violations**
109
+
110
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec rubocop`
111
+ Expected: May have new violations from rubocop-rspec and shorter line length. Fix any that appear.
112
+
113
+ - [ ] **Step 5: Auto-fix what's possible, manually fix the rest**
114
+
115
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec rubocop -a`
116
+ Then fix any remaining violations manually. If specific cops are too noisy for existing code, add targeted exclusions to `.rubocop.yml` rather than rewriting working code.
117
+
118
+ - [ ] **Step 6: Verify clean rubocop run**
119
+
120
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec rubocop`
121
+ Expected: 0 offenses
122
+
123
+ - [ ] **Step 7: Run rspec to ensure nothing broke**
124
+
125
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec rspec`
126
+ Expected: All tests pass
127
+
128
+ - [ ] **Step 8: Commit**
129
+
130
+ ```bash
131
+ cd /Users/jravaliya/Code/njtransit
132
+ git add Gemfile Gemfile.lock .rubocop.yml lib/ spec/
133
+ git commit -m "chore: tighten rubocop config, add rubocop-rspec
134
+
135
+ Reduce max line length to 120, add rubocop-rspec for spec linting.
136
+ Fix all resulting violations.
137
+
138
+ Co-Authored-By: Claude <noreply@anthropic.com>"
139
+ ```
140
+
141
+ ---
142
+
143
+ ### Task 2: GitHub Actions CI Upgrade
144
+
145
+ **Files:**
146
+ - Modify: `.github/workflows/ci.yml`
147
+ - Create: `.github/dependabot.yml`
148
+
149
+ - [ ] **Step 1: Create .ruby-version file**
150
+
151
+ Create `.ruby-version` with content `3.2` (needed for CI cache key and ruby/setup-ruby auto-detection).
152
+
153
+ - [ ] **Step 2: Upgrade ci.yml to match tenor patterns**
154
+
155
+ Replace `.github/workflows/ci.yml` with:
156
+ ```yaml
157
+ name: CI
158
+
159
+ on:
160
+ pull_request:
161
+ push:
162
+ branches: [main]
163
+
164
+ jobs:
165
+ lint:
166
+ runs-on: ubuntu-latest
167
+ env:
168
+ RUBOCOP_CACHE_ROOT: tmp/rubocop
169
+ steps:
170
+ - name: Checkout code
171
+ uses: actions/checkout@v4
172
+
173
+ - name: Set up Ruby
174
+ uses: ruby/setup-ruby@v1
175
+ with:
176
+ bundler-cache: true
177
+
178
+ - name: Prepare RuboCop cache
179
+ uses: actions/cache@v4
180
+ env:
181
+ DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', 'Gemfile.lock') }}
182
+ with:
183
+ path: ${{ env.RUBOCOP_CACHE_ROOT }}
184
+ key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
185
+ restore-keys: |
186
+ rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-
187
+
188
+ - name: Lint code for consistent style
189
+ run: bundle exec rubocop --parallel -f github
190
+
191
+ test:
192
+ runs-on: ubuntu-latest
193
+ strategy:
194
+ matrix:
195
+ ruby-version: ["3.2", "3.3"]
196
+ steps:
197
+ - name: Checkout code
198
+ uses: actions/checkout@v4
199
+
200
+ - name: Set up Ruby ${{ matrix.ruby-version }}
201
+ uses: ruby/setup-ruby@v1
202
+ with:
203
+ ruby-version: ${{ matrix.ruby-version }}
204
+ bundler-cache: true
205
+
206
+ - name: Run RSpec
207
+ run: bundle exec rspec
208
+ ```
209
+
210
+ Key changes from current: RuboCop caching with `actions/cache@v4`, GitHub-format output (`--parallel -f github`), `.ruby-version` auto-detection, triggers on all PRs (not just to main).
211
+
212
+ - [ ] **Step 2: Create dependabot.yml**
213
+
214
+ Create `.github/dependabot.yml`:
215
+ ```yaml
216
+ version: 2
217
+ updates:
218
+ - package-ecosystem: bundler
219
+ directory: "/"
220
+ schedule:
221
+ interval: weekly
222
+ open-pull-requests-limit: 10
223
+ - package-ecosystem: github-actions
224
+ directory: "/"
225
+ schedule:
226
+ interval: weekly
227
+ open-pull-requests-limit: 10
228
+ ```
229
+
230
+ - [ ] **Step 4: Commit**
231
+
232
+ ```bash
233
+ cd /Users/jravaliya/Code/njtransit
234
+ git add .ruby-version .github/workflows/ci.yml .github/dependabot.yml
235
+ git commit -m "chore: upgrade CI to match tenor patterns
236
+
237
+ Add RuboCop caching, update to actions/checkout@v6, add dependabot
238
+ for weekly bundler and GH Actions dependency updates.
239
+
240
+ Co-Authored-By: Claude <noreply@anthropic.com>"
241
+ ```
242
+
243
+ ---
244
+
245
+ ### Task 3: Lefthook Alignment
246
+
247
+ **Files:**
248
+ - Modify: `lefthook.yml`
249
+
250
+ - [ ] **Step 1: Add parallel flag to pre-push**
251
+
252
+ Update `lefthook.yml` to:
253
+ ```yaml
254
+ # Lefthook configuration for git hooks
255
+ # Install hooks: bundle exec lefthook install
256
+ # Docs: https://github.com/evilmartians/lefthook
257
+
258
+ pre-commit:
259
+ parallel: true
260
+ commands:
261
+ rubocop:
262
+ glob: "*.rb"
263
+ run: bundle exec rubocop --force-exclusion {staged_files}
264
+ stage_fixed: true
265
+
266
+ pre-push:
267
+ parallel: true
268
+ commands:
269
+ rspec:
270
+ run: bundle exec rspec
271
+ ```
272
+
273
+ Only change: added `parallel: true` to pre-push block (matches tenor).
274
+
275
+ - [ ] **Step 2: Verify hooks work**
276
+
277
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec lefthook run pre-commit`
278
+ Expected: rubocop runs on staged files
279
+
280
+ Run: `cd /Users/jravaliya/Code/njtransit && bundle exec lefthook run pre-push`
281
+ Expected: rspec runs
282
+
283
+ - [ ] **Step 3: Commit**
284
+
285
+ ```bash
286
+ cd /Users/jravaliya/Code/njtransit
287
+ git add lefthook.yml
288
+ git commit -m "chore: align lefthook config with tenor patterns
289
+
290
+ Add parallel: true to pre-push block.
291
+
292
+ Co-Authored-By: Claude <noreply@anthropic.com>"
293
+ ```
294
+
295
+ ---
296
+
297
+ ### Task 4: Claude Code NJTransit Terminal Agent
298
+
299
+ **Files:**
300
+ - Create: `.claude/commands/njtransit.md`
301
+
302
+ This is the creative centerpiece. The slash command teaches Claude how to use the njtransit gem to answer real-time transit questions from the terminal.
303
+
304
+ - [ ] **Step 1: Create the commands directory**
305
+
306
+ Run: `mkdir -p /Users/jravaliya/Code/njtransit/.claude/commands`
307
+
308
+ - [ ] **Step 2: Create the agent command file**
309
+
310
+ Create `.claude/commands/njtransit.md`:
311
+
312
+ ````markdown
313
+ ---
314
+ description: Ask NJ Transit questions - bus arrivals, routes, stops, schedules, light rail. Uses the njtransit gem to query real-time data.
315
+ ---
316
+
317
+ You are an NJ Transit assistant. The user is asking a transit question from their terminal. Your job is to answer it by writing and executing Ruby code using the `njtransit` gem in this repository.
318
+
319
+ ## Setup
320
+
321
+ The gem is in the current directory. Credentials come from environment variables. Before making any API call, run this setup:
322
+
323
+ ```ruby
324
+ require "dotenv"
325
+ Dotenv.load
326
+
327
+ $LOAD_PATH.unshift(File.join(Dir.pwd, "lib"))
328
+ require "njtransit"
329
+
330
+ NJTransit.configure do |config|
331
+ config.username = ENV["NJTRANSIT_USERNAME"]
332
+ config.password = ENV["NJTRANSIT_PASSWORD"]
333
+ end
334
+
335
+ client = NJTransit.client
336
+ ```
337
+
338
+ If credentials are missing, tell the user to set `NJTRANSIT_USERNAME` and `NJTRANSIT_PASSWORD` in their `.env` file. They can register at https://developer.njtransit.com/registration
339
+
340
+ ## Available API Methods
341
+
342
+ ### Bus & Light Rail Routes
343
+ ```ruby
344
+ # Get all routes (mode: "BUS", "NLR", "HBLR", "RL", or "ALL")
345
+ # Currently the gem hardcodes BUS mode, so pass mode directly:
346
+ client.bus.routes # BUS routes only
347
+ ```
348
+
349
+ ### Directions for a Route
350
+ ```ruby
351
+ client.bus.directions(route: "197")
352
+ # => [{"Direction_1"=>"New York", "Direction_2"=>"Willowbrook Mall"}]
353
+ ```
354
+
355
+ ### Stops on a Route
356
+ ```ruby
357
+ client.bus.stops(route: "197", direction: "New York", enrich: false)
358
+ # => [{"busstopdescription"=>"...", "busstopnumber"=>"..."}]
359
+ # Use name_contains: "keyword" to filter
360
+ ```
361
+
362
+ ### Stop Name Lookup
363
+ ```ruby
364
+ client.bus.stop_name(stop_number: "19159", enrich: false)
365
+ # => {"stopName"=>"15TH AVE AT BEDFORD ST"}
366
+ ```
367
+
368
+ ### Real-Time Departures (most useful for "when is my bus?")
369
+ ```ruby
370
+ client.bus.departures(stop: "PABT", enrich: false)
371
+ # Returns trips active in next hour
372
+ # Can filter: route: "197", direction: "New York"
373
+ # Response includes: public_route, header (destination), departuretime ("in 18 mins"),
374
+ # lanegate, vehicle_id, passload, sched_dep_time
375
+ ```
376
+
377
+ ### Route Trips at a Location
378
+ ```ruby
379
+ client.bus.route_trips(location: "PABT", route: "113")
380
+ # Returns scheduled trips with departure times, lane/gate info
381
+ ```
382
+
383
+ ### Trip Stops (track a specific bus)
384
+ ```ruby
385
+ client.bus.trip_stops(
386
+ internal_trip_number: "19624134",
387
+ sched_dep_time: "6/22/2023 12:50:00 AM"
388
+ )
389
+ # Returns every stop the trip makes with status (Departed/Approaching/etc)
390
+ ```
391
+
392
+ ### Nearby Stops
393
+ ```ruby
394
+ client.bus.stops_nearby(lat: 40.8523, lon: -74.2567, radius: 2000, enrich: false)
395
+ # radius is in feet. Returns stops with distance
396
+ ```
397
+
398
+ ### Nearby Vehicles
399
+ ```ruby
400
+ client.bus.vehicles_nearby(lat: 40.8523, lon: -74.2567, radius: 5000, enrich: false)
401
+ # Returns live vehicle positions with route, destination, passenger load
402
+ ```
403
+
404
+ ### Locations (terminals/hubs)
405
+ ```ruby
406
+ client.bus.locations
407
+ # => [{"bus_terminal_code"=>"PABT", "bus_terminal_name"=>"Port Authority Bus Terminal"}, ...]
408
+ ```
409
+
410
+ ### GTFS Static Data (schedules, offline queries)
411
+ ```ruby
412
+ # Only if GTFS data has been imported
413
+ gtfs = NJTransit::GTFS.new
414
+ gtfs.stops.find_by_code("WBRK") # Find a stop
415
+ gtfs.routes.find("197") # Find a route
416
+ gtfs.routes_between(from: "WBRK", to: "PABT") # Routes connecting two stops
417
+ gtfs.schedule(route: "197", stop: "WBRK", date: Date.today) # Full day schedule
418
+ ```
419
+
420
+ ## Common Terminal Codes
421
+
422
+ - **PABT** — Port Authority Bus Terminal (Manhattan)
423
+ - **NWKP** — Newark Penn Station
424
+ - **JCSQ** — Journal Square
425
+ - **HOBO** — Hoboken Terminal
426
+ - **SECC** — Secaucus Junction
427
+
428
+ ## How to Answer Questions
429
+
430
+ 1. **"When is my next bus?"** → Use `departures` with the stop and optionally route. Show departure times, destinations, and vehicle info in a clean table.
431
+
432
+ 2. **"What buses go from X to Y?"** → Use GTFS `routes_between` if available, otherwise use `routes` + `stops` to find matching routes.
433
+
434
+ 3. **"What stops are near me?"** → Ask for their location or use a known landmark's coordinates with `stops_nearby`.
435
+
436
+ 4. **"Where is the 197 bus right now?"** → Use `vehicles_nearby` with a wide radius, filtered by route in the results.
437
+
438
+ 5. **"What's the schedule for route X?"** → Use `departures` for real-time, or GTFS `schedule` for the full day.
439
+
440
+ ## Response Style
441
+
442
+ - Be concise — the user is in a terminal, probably in a hurry
443
+ - Format departure times as a clean table
444
+ - Highlight the NEXT departure prominently
445
+ - Include passenger load info when available (EMPTY, LIGHT, MODERATE, HEAVY)
446
+ - If enrichment fails (GTFS not imported), use `enrich: false` and still return results
447
+ - Always use `enrich: false` to avoid GTFS dependency unless the user specifically asks for enriched data
448
+
449
+ ## User's Question
450
+
451
+ $ARGUMENTS
452
+ ````
453
+
454
+ - [ ] **Step 3: Test the command works**
455
+
456
+ From the njtransit directory, run: `/njtransit what routes are available?`
457
+ Verify Claude picks up the command and attempts to use the gem.
458
+
459
+ - [ ] **Step 4: Commit**
460
+
461
+ ```bash
462
+ cd /Users/jravaliya/Code/njtransit
463
+ git add .claude/commands/njtransit.md
464
+ git commit -m "feat: add Claude Code terminal agent for NJ Transit queries
465
+
466
+ New /njtransit slash command lets you ask transit questions from terminal.
467
+ Supports real-time departures, route lookup, nearby stops, vehicle tracking,
468
+ and GTFS schedule queries via the njtransit gem.
469
+
470
+ Co-Authored-By: Claude <noreply@anthropic.com>"
471
+ ```
472
+
473
+ ---
474
+
475
+ ## Execution Order
476
+
477
+ Tasks 1-3 are independent infrastructure and can be parallelized.
478
+ Task 4 (agent) is also independent but is the most creative piece.
479
+
480
+ Recommended: run all 4 in parallel if using subagent-driven development.
data/lefthook.yml ADDED
@@ -0,0 +1,17 @@
1
+ # Lefthook configuration for git hooks
2
+ # Install hooks: bundle exec lefthook install
3
+ # Docs: https://github.com/evilmartians/lefthook
4
+
5
+ pre-commit:
6
+ parallel: true
7
+ commands:
8
+ rubocop:
9
+ glob: "*.rb"
10
+ run: bundle exec rubocop --force-exclusion {staged_files}
11
+ stage_fixed: true
12
+
13
+ pre-push:
14
+ parallel: true
15
+ commands:
16
+ rspec:
17
+ run: bundle exec rspec