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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.40] - 2026-04-06

### Fixed

- **`get_controller` concern modules indexed as controllers**: Files in `app/controllers/concerns/` (e.g. `Authenticatable`, `Respondable`) were classified as category 2 (controllers) by the scanner, causing the indexer to add them to `extractions.controllers`. The scanner now classifies controller concern files as `design_patterns` (category 26) before the broad controller rule matches. Fixes false-positive controller discovery in 3 eval apps (sharp, quarter-turn, lf-api)
- **`get_controller` superclass with `::` prefix not detected**: Controller class declarations inheriting with a root-namespace prefix (e.g. `class UploaderController < ::Spree::Api::V2::BaseController`) failed to match the `classDeclaration` regex, resulting in `null` superclass and incorrect `api_controller` flag. The pattern now handles optional leading `::` on the superclass

## [1.0.39] - 2026-04-05

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reinteractive/rails-insight",
"version": "1.0.39",
"version": "1.0.40",
"description": "Rails-aware codebase indexer — MCP server for AI agents",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion src/core/patterns/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Regex patterns for Rails controller extraction.
*/
export const CONTROLLER_PATTERNS = {
classDeclaration: /class\s+(\w+(?:::\w+)*Controller)\s*<\s*(\w+(?:::\w+)*)/,
classDeclaration: /class\s+(\w+(?:::\w+)*Controller)\s*<\s*(?:::)?(\w+(?:::\w+)*)/,
include: /^\s*include\s+(\w+(?:::\w+)*)/m,

// Filters
Expand Down
4 changes: 4 additions & 0 deletions src/core/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ const RULES = [

// --- Core Tier 1: broad app/ directory matches ---
{ test: (p) => /^app\/models\/.*\.rb$/.test(p), category: 1 },
{
test: (p) => /^app\/controllers\/concerns\/.*\.rb$/.test(p),
category: 26,
},
{ test: (p) => /^app\/controllers\/.*\.rb$/.test(p), category: 2 },
{ test: (p) => /^app\/components\/.*\.(rb|html\.\w+)$/.test(p), category: 5 },
{
Expand Down
12 changes: 12 additions & 0 deletions test/core/scanner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ describe('Scanner', () => {
expect(entry.category).toBe(2)
})

it('classifies controller concern files as design_patterns, not controllers', () => {
const entry = classifyFile('app/controllers/concerns/authenticatable.rb')
expect(entry.category).toBe(26)
expect(entry.categoryName).toBe('design_patterns')
})

it('classifies nested controller concern files as design_patterns', () => {
const entry = classifyFile('app/controllers/concerns/api/respondable.rb')
expect(entry.category).toBe(26)
expect(entry.categoryName).toBe('design_patterns')
})

it('classifies routes', () => {
expect(classifyFile('config/routes.rb').category).toBe(3)
expect(classifyFile('config/routes/api.rb').category).toBe(3)
Expand Down
30 changes: 30 additions & 0 deletions test/extractors/controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,4 +551,34 @@ end`
expect(tqp.options).toContain(']')
})
})

describe('superclass with :: prefix', () => {
it('extracts superclass when prefixed with ::', () => {
const content = `module Spree
module Admin
module Api
module V1
class UploaderController < ::Spree::Api::V2::BaseController
def image
blob = ActiveStorage::Blob.create_after_upload!(io: params[:file])
render json: { url: blob.url }
end
end
end
end
end
end`
const result = extractController(
mockProvider({
'app/controllers/spree/admin/api/v1/uploader_controller.rb': content,
}),
'app/controllers/spree/admin/api/v1/uploader_controller.rb',
)
expect(result).not.toBeNull()
expect(result.class).toBe('Spree::Admin::Api::V1::UploaderController')
expect(result.superclass).toBe('Spree::Api::V2::BaseController')
expect(result.api_controller).toBe(true)
expect(result.actions).toContain('image')
})
})
})
9 changes: 9 additions & 0 deletions test/patterns/controller-patterns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ describe('CONTROLLER_PATTERNS', () => {
CONTROLLER_PATTERNS.classDeclaration,
)
})
it('matches controller with :: prefix on superclass', () => {
const m =
'class UploaderController < ::Spree::Api::V2::BaseController'.match(
CONTROLLER_PATTERNS.classDeclaration,
)
expect(m).not.toBeNull()
expect(m[1]).toBe('UploaderController')
expect(m[2]).toBe('Spree::Api::V2::BaseController')
})
})

describe('filterType', () => {
Expand Down
Loading