aidp 0.5.0 ā 0.7.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.
- checksums.yaml +4 -4
- data/README.md +132 -1
- data/lib/aidp/analysis/kb_inspector.rb +456 -0
- data/lib/aidp/analysis/seams.rb +188 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
- data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
- data/lib/aidp/analyze/data_retention_manager.rb +0 -5
- data/lib/aidp/analyze/error_handler.rb +0 -3
- data/lib/aidp/analyze/export_manager.rb +0 -7
- data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
- data/lib/aidp/analyze/memory_manager.rb +34 -60
- data/lib/aidp/analyze/parallel_processor.rb +0 -6
- data/lib/aidp/analyze/performance_optimizer.rb +0 -3
- data/lib/aidp/analyze/repository_chunker.rb +14 -21
- data/lib/aidp/analyze/steps.rb +6 -0
- data/lib/aidp/analyze/storage.rb +0 -7
- data/lib/aidp/analyze/tool_configuration.rb +21 -36
- data/lib/aidp/cli/jobs_command.rb +9 -9
- data/lib/aidp/cli.rb +56 -0
- data/lib/aidp/jobs/base_job.rb +0 -2
- data/lib/aidp/jobs/provider_execution_job.rb +11 -24
- data/lib/aidp/providers/agent_supervisor.rb +2 -2
- data/lib/aidp/providers/anthropic.rb +15 -21
- data/lib/aidp/providers/cursor.rb +2 -4
- data/lib/aidp/providers/gemini.rb +4 -6
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +6 -0
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- metadata +22 -4
- data/lib/aidp/database_migration.rb +0 -158
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e32a892070257857ac578c965a7aa1bb240a1a40a58f884bc6740a5aa6a57804
|
4
|
+
data.tar.gz: cfca9c65bc0d3b5e784fbab8dcff12545982d5bb00057e575dfdfcd77007accf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99c4b48fce7af4bc8a043549fcb4ba0119dc84af870fdb8b0aae26045c4626e16cbd035df94e8b5d5c638d10a1a26f3a76b7c260647b701a55ef1f795a373ca0
|
7
|
+
data.tar.gz: 818054559441866f659d77d5b0e10721822c3aa5abc74010e90f5cbbb13a3d1fe85355fb6e2f82a867e24ed4b24e71670a72370b23c69526bf0400f7a1bc2a6a
|
data/README.md
CHANGED
@@ -98,6 +98,114 @@ AIDP_PROVIDER=anthropic aidp execute next
|
|
98
98
|
AIDP_LLM_CMD=/usr/local/bin/claude aidp execute next
|
99
99
|
```
|
100
100
|
|
101
|
+
## Tree-sitter Static Analysis
|
102
|
+
|
103
|
+
AIDP includes powerful Tree-sitter-based static analysis capabilities for code.
|
104
|
+
|
105
|
+
### Tree-sitter Dependencies
|
106
|
+
|
107
|
+
The Tree-sitter analysis requires the Tree-sitter system library and pre-compiled language parsers:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
# Install Tree-sitter system library
|
111
|
+
# macOS
|
112
|
+
brew install tree-sitter
|
113
|
+
|
114
|
+
# Ubuntu/Debian
|
115
|
+
sudo apt-get install tree-sitter
|
116
|
+
|
117
|
+
# Or follow the ruby_tree_sitter README for other platforms
|
118
|
+
# https://github.com/Faveod/ruby-tree-sitter#installation
|
119
|
+
|
120
|
+
# Install Tree-sitter parsers
|
121
|
+
./install_tree_sitter_parsers.sh
|
122
|
+
```
|
123
|
+
|
124
|
+
### Parser Installation Script
|
125
|
+
|
126
|
+
The `install_tree_sitter_parsers.sh` script automatically downloads and installs pre-built Tree-sitter parsers:
|
127
|
+
|
128
|
+
```bash
|
129
|
+
# Make the script executable
|
130
|
+
chmod +x install_tree_sitter_parsers.sh
|
131
|
+
|
132
|
+
# Run the installation script
|
133
|
+
./install_tree_sitter_parsers.sh
|
134
|
+
```
|
135
|
+
|
136
|
+
The script will:
|
137
|
+
|
138
|
+
- Detect your OS and architecture (macOS ARM64, Linux x64, etc.)
|
139
|
+
- Download the appropriate parser bundle from [Faveod/tree-sitter-parsers](https://github.com/Faveod/tree-sitter-parsers/releases/tag/v4.9)
|
140
|
+
- Extract parsers to `.aidp/parsers/` directory
|
141
|
+
- Set up the `TREE_SITTER_PARSERS` environment variable
|
142
|
+
|
143
|
+
### Environment Setup
|
144
|
+
|
145
|
+
After running the installation script, make the environment variable permanent:
|
146
|
+
|
147
|
+
```bash
|
148
|
+
# Add to your shell profile (e.g., ~/.zshrc, ~/.bashrc)
|
149
|
+
echo 'export TREE_SITTER_PARSERS="$(pwd)/.aidp/parsers"' >> ~/.zshrc
|
150
|
+
|
151
|
+
# Reload your shell
|
152
|
+
source ~/.zshrc
|
153
|
+
```
|
154
|
+
|
155
|
+
### Tree-sitter Analysis Commands
|
156
|
+
|
157
|
+
```bash
|
158
|
+
# Run Tree-sitter static analysis
|
159
|
+
aidp analyze code
|
160
|
+
|
161
|
+
# Analyze specific languages
|
162
|
+
aidp analyze code --langs ruby,javascript,typescript
|
163
|
+
|
164
|
+
# Use multiple threads for faster analysis
|
165
|
+
aidp analyze code --threads 8
|
166
|
+
|
167
|
+
# Rebuild knowledge base from scratch
|
168
|
+
aidp analyze code --rebuild
|
169
|
+
|
170
|
+
# Specify custom KB directory
|
171
|
+
aidp analyze code --kb-dir .aidp/custom-kb
|
172
|
+
|
173
|
+
# Inspect generated knowledge base
|
174
|
+
aidp kb show
|
175
|
+
|
176
|
+
# Show specific KB data
|
177
|
+
aidp kb show symbols
|
178
|
+
aidp kb show imports
|
179
|
+
aidp kb show seams
|
180
|
+
|
181
|
+
# Generate dependency graphs
|
182
|
+
aidp kb graph imports
|
183
|
+
aidp kb graph calls
|
184
|
+
```
|
185
|
+
|
186
|
+
### Knowledge Base Structure
|
187
|
+
|
188
|
+
The Tree-sitter analysis generates structured JSON files in `.aidp/kb/`:
|
189
|
+
|
190
|
+
- **`symbols.json`** - Classes, modules, methods, and their metadata
|
191
|
+
- **`imports.json`** - Require statements and dependencies
|
192
|
+
- **`calls.json`** - Method calls and invocation patterns
|
193
|
+
- **`metrics.json`** - Code complexity and size metrics
|
194
|
+
- **`seams.json`** - Integration points and dependency injection opportunities
|
195
|
+
- **`hotspots.json`** - Frequently changed code areas (based on git history)
|
196
|
+
- **`tests.json`** - Test coverage analysis
|
197
|
+
- **`cycles.json`** - Circular dependency detection
|
198
|
+
|
199
|
+
### Legacy Code Analysis Features
|
200
|
+
|
201
|
+
The Tree-sitter analysis specifically supports:
|
202
|
+
|
203
|
+
- **Seam Detection**: Identifies I/O operations, global state access, and constructor dependencies
|
204
|
+
- **Change Hotspots**: Uses git history to identify frequently modified code
|
205
|
+
- **Dependency Analysis**: Maps import relationships and call graphs
|
206
|
+
- **Test Coverage**: Identifies untested public APIs
|
207
|
+
- **Refactoring Opportunities**: Suggests dependency injection points and seam locations
|
208
|
+
|
101
209
|
## Background Jobs
|
102
210
|
|
103
211
|
AIDP uses background jobs to handle all AI provider executions, providing better reliability and real-time monitoring capabilities.
|
@@ -217,9 +325,19 @@ aidp execute next
|
|
217
325
|
# Install dependencies
|
218
326
|
bundle install
|
219
327
|
|
328
|
+
# Install Tree-sitter parsers for development
|
329
|
+
./install_tree_sitter_parsers.sh
|
330
|
+
|
331
|
+
# Set up environment variables
|
332
|
+
export TREE_SITTER_PARSERS="$(pwd)/.aidp/parsers"
|
333
|
+
|
220
334
|
# Run tests
|
221
335
|
bundle exec rspec
|
222
336
|
|
337
|
+
# Run Tree-sitter analysis tests specifically
|
338
|
+
bundle exec rspec spec/aidp/analysis/
|
339
|
+
bundle exec rspec spec/integration/tree_sitter_analysis_workflow_spec.rb
|
340
|
+
|
223
341
|
# Run linter
|
224
342
|
bundle exec standardrb
|
225
343
|
|
@@ -230,6 +348,19 @@ bundle exec standardrb --fix
|
|
230
348
|
bundle exec rake build
|
231
349
|
```
|
232
350
|
|
351
|
+
### Development Dependencies
|
352
|
+
|
353
|
+
The following system dependencies are required for development:
|
354
|
+
|
355
|
+
- **Tree-sitter** - System library for parsing (install via `brew install tree-sitter` or package manager)
|
356
|
+
- **PostgreSQL** - Database for job management
|
357
|
+
- **Ruby gems** - All required gems are specified in `aidp.gemspec` and installed via `bundle install`
|
358
|
+
|
359
|
+
Optional gems with fallbacks:
|
360
|
+
|
361
|
+
- **`concurrent-ruby`** - Parallel processing (fallback to basic threading if not available)
|
362
|
+
- **`tty-table`** - Table rendering (fallback to basic ASCII tables if not available)
|
363
|
+
|
233
364
|
## Contributing
|
234
365
|
|
235
366
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and conventional commit guidelines.
|
@@ -254,7 +385,7 @@ The gem automates a complete 15-step development pipeline:
|
|
254
385
|
- **Threat Model** ā Security analysis (`docs/ThreatModel.md`)
|
255
386
|
- **Test Plan** ā Testing strategy (`docs/TestPlan.md`)
|
256
387
|
- **Scaffolding** ā Project structure guidance (`docs/ScaffoldingGuide.md`)
|
257
|
-
- **Static Analysis** ā Code quality tools (`docs/StaticAnalysis.md
|
388
|
+
- **Static Analysis** ā Code quality tools and Tree-sitter analysis (`docs/StaticAnalysis.md`, `.aidp/kb/`)
|
258
389
|
- **Observability** ā Monitoring and SLOs (`docs/Observability.md`)
|
259
390
|
- **Delivery** ā Deployment strategy (`docs/DeliveryPlan.md`)
|
260
391
|
- **Docs Portal** ā Documentation portal (`docs/DocsPortalPlan.md`)
|
@@ -0,0 +1,456 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "tty-table"
|
5
|
+
|
6
|
+
module Aidp
|
7
|
+
module Analysis
|
8
|
+
class KBInspector
|
9
|
+
def initialize(kb_dir = ".aidp/kb")
|
10
|
+
@kb_dir = File.expand_path(kb_dir)
|
11
|
+
@data = load_kb_data
|
12
|
+
end
|
13
|
+
|
14
|
+
def show(type, format: "summary")
|
15
|
+
case type
|
16
|
+
when "seams"
|
17
|
+
show_seams(format)
|
18
|
+
when "hotspots"
|
19
|
+
show_hotspots(format)
|
20
|
+
when "cycles"
|
21
|
+
show_cycles(format)
|
22
|
+
when "apis"
|
23
|
+
show_apis(format)
|
24
|
+
when "symbols"
|
25
|
+
show_symbols(format)
|
26
|
+
when "imports"
|
27
|
+
show_imports(format)
|
28
|
+
when "summary"
|
29
|
+
show_summary(format)
|
30
|
+
else
|
31
|
+
puts "Unknown KB type: #{type}"
|
32
|
+
puts "Available types: seams, hotspots, cycles, apis, symbols, imports, summary"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_graph(type, format: "dot", output: nil)
|
37
|
+
case type
|
38
|
+
when "imports"
|
39
|
+
generate_import_graph(format, output)
|
40
|
+
when "calls"
|
41
|
+
generate_call_graph(format, output)
|
42
|
+
when "cycles"
|
43
|
+
generate_cycle_graph(format, output)
|
44
|
+
else
|
45
|
+
puts "Unknown graph type: #{type}"
|
46
|
+
puts "Available types: imports, calls, cycles"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def truncate_text(text, max_length = 50)
|
53
|
+
return nil unless text
|
54
|
+
return text if text.length <= max_length
|
55
|
+
|
56
|
+
text[0..max_length - 4] + "..."
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_table(header, rows)
|
60
|
+
TTY::Table.new(header: header, rows: rows)
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_kb_data
|
64
|
+
data = {}
|
65
|
+
|
66
|
+
%w[symbols imports calls metrics seams hotspots tests cycles].each do |type|
|
67
|
+
file_path = File.join(@kb_dir, "#{type}.json")
|
68
|
+
if File.exist?(file_path)
|
69
|
+
begin
|
70
|
+
data[type.to_sym] = JSON.parse(File.read(file_path), symbolize_names: true)
|
71
|
+
rescue JSON::ParserError => e
|
72
|
+
puts "Warning: Could not parse #{file_path}: #{e.message}"
|
73
|
+
data[type.to_sym] = []
|
74
|
+
end
|
75
|
+
else
|
76
|
+
data[type.to_sym] = []
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
data
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_summary(_format)
|
84
|
+
puts "\nš Knowledge Base Summary"
|
85
|
+
puts "=" * 50
|
86
|
+
|
87
|
+
puts "š KB Directory: #{@kb_dir}"
|
88
|
+
puts "š Files analyzed: #{count_files}"
|
89
|
+
puts "šļø Symbols: #{@data[:symbols]&.length || 0}"
|
90
|
+
puts "š¦ Imports: #{@data[:imports]&.length || 0}"
|
91
|
+
puts "š Calls: #{@data[:calls]&.length || 0}"
|
92
|
+
puts "š Metrics: #{@data[:metrics]&.length || 0}"
|
93
|
+
puts "š§ Seams: #{@data[:seams]&.length || 0}"
|
94
|
+
puts "š„ Hotspots: #{@data[:hotspots]&.length || 0}"
|
95
|
+
puts "š§Ŗ Tests: #{@data[:tests]&.length || 0}"
|
96
|
+
puts "š Cycles: #{@data[:cycles]&.length || 0}"
|
97
|
+
|
98
|
+
if @data[:seams]&.any?
|
99
|
+
puts "\nš§ Seam Types:"
|
100
|
+
seam_types = @data[:seams].group_by { |s| s[:kind] }
|
101
|
+
seam_types.each do |type, seams|
|
102
|
+
puts " #{type}: #{seams.length}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if @data[:hotspots]&.any?
|
107
|
+
puts "\nš„ Top 5 Hotspots:"
|
108
|
+
@data[:hotspots].first(5).each_with_index do |hotspot, i|
|
109
|
+
puts " #{i + 1}. #{hotspot[:file]}:#{hotspot[:method]} (score: #{hotspot[:score]})"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def show_seams(format)
|
115
|
+
return puts "No seams data available" unless @data[:seams]&.any?
|
116
|
+
|
117
|
+
case format
|
118
|
+
when "json"
|
119
|
+
puts JSON.pretty_generate(@data[:seams])
|
120
|
+
when "table"
|
121
|
+
show_seams_table
|
122
|
+
else
|
123
|
+
show_seams_summary
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def show_seams_table
|
128
|
+
table = create_table(
|
129
|
+
["Type", "File", "Line", "Symbol", "Suggestion"],
|
130
|
+
@data[:seams].map do |seam|
|
131
|
+
[
|
132
|
+
seam[:kind],
|
133
|
+
seam[:file],
|
134
|
+
seam[:line],
|
135
|
+
seam[:symbol_id]&.split(":")&.last || "N/A",
|
136
|
+
truncate_text(seam[:suggestion], 50) || "N/A"
|
137
|
+
]
|
138
|
+
end
|
139
|
+
)
|
140
|
+
|
141
|
+
puts "\nš§ Seams Analysis"
|
142
|
+
puts "=" * 80
|
143
|
+
puts table.render
|
144
|
+
end
|
145
|
+
|
146
|
+
def show_seams_summary
|
147
|
+
puts "\nš§ Seams Analysis"
|
148
|
+
puts "=" * 50
|
149
|
+
|
150
|
+
seam_types = @data[:seams].group_by { |s| s[:kind] }
|
151
|
+
|
152
|
+
seam_types.each do |type, seams|
|
153
|
+
puts "\nš #{type.upcase} (#{seams.length} found)"
|
154
|
+
puts "-" * 30
|
155
|
+
|
156
|
+
seams.first(10).each do |seam|
|
157
|
+
puts " #{seam[:file]}:#{seam[:line]}"
|
158
|
+
puts " Symbol: #{seam[:symbol_id]&.split(":")&.last}"
|
159
|
+
puts " Suggestion: #{seam[:suggestion]}"
|
160
|
+
puts
|
161
|
+
end
|
162
|
+
|
163
|
+
if seams.length > 10
|
164
|
+
puts " ... and #{seams.length - 10} more"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def show_hotspots(format)
|
170
|
+
return puts "No hotspots data available" unless @data[:hotspots]&.any?
|
171
|
+
|
172
|
+
case format
|
173
|
+
when "json"
|
174
|
+
puts JSON.pretty_generate(@data[:hotspots])
|
175
|
+
when "table"
|
176
|
+
show_hotspots_table
|
177
|
+
else
|
178
|
+
show_hotspots_summary
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def show_hotspots_table
|
183
|
+
table = create_table(
|
184
|
+
["Rank", "File", "Method", "Score", "Complexity", "Touches"],
|
185
|
+
@data[:hotspots].map.with_index do |hotspot, i|
|
186
|
+
[
|
187
|
+
i + 1,
|
188
|
+
hotspot[:file],
|
189
|
+
hotspot[:method],
|
190
|
+
hotspot[:score],
|
191
|
+
hotspot[:complexity],
|
192
|
+
hotspot[:touches]
|
193
|
+
]
|
194
|
+
end
|
195
|
+
)
|
196
|
+
|
197
|
+
puts "\nš„ Code Hotspots"
|
198
|
+
puts "=" * 80
|
199
|
+
puts table.render
|
200
|
+
end
|
201
|
+
|
202
|
+
def show_hotspots_summary
|
203
|
+
puts "\nš„ Code Hotspots (Top 20)"
|
204
|
+
puts "=" * 50
|
205
|
+
|
206
|
+
@data[:hotspots].each_with_index do |hotspot, i|
|
207
|
+
puts "#{i + 1}. #{hotspot[:file]}:#{hotspot[:method]}"
|
208
|
+
puts " Score: #{hotspot[:score]} (Complexity: #{hotspot[:complexity]}, Touches: #{hotspot[:touches]})"
|
209
|
+
puts
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def show_cycles(format)
|
214
|
+
return puts "No cycles data available" unless @data[:cycles]&.any?
|
215
|
+
|
216
|
+
case format
|
217
|
+
when "json"
|
218
|
+
puts JSON.pretty_generate(@data[:cycles])
|
219
|
+
else
|
220
|
+
show_cycles_summary
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def show_cycles_summary
|
225
|
+
puts "\nš Import Cycles"
|
226
|
+
puts "=" * 50
|
227
|
+
|
228
|
+
@data[:cycles].each_with_index do |cycle, i|
|
229
|
+
puts "Cycle #{i + 1}:"
|
230
|
+
cycle[:members].each do |member|
|
231
|
+
puts " - #{member}"
|
232
|
+
end
|
233
|
+
puts " Weight: #{cycle[:weight]}" if cycle[:weight]
|
234
|
+
puts
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def show_apis(format)
|
239
|
+
return puts "No APIs data available" unless @data[:tests]&.any?
|
240
|
+
|
241
|
+
untested_apis = @data[:tests].select { |t| t[:tests].empty? }
|
242
|
+
|
243
|
+
case format
|
244
|
+
when "json"
|
245
|
+
puts JSON.pretty_generate(untested_apis)
|
246
|
+
else
|
247
|
+
show_apis_summary(untested_apis)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def show_apis_summary(untested_apis)
|
252
|
+
puts "\nš§Ŗ Untested Public APIs"
|
253
|
+
puts "=" * 50
|
254
|
+
|
255
|
+
if untested_apis.empty?
|
256
|
+
puts "ā
All public APIs have associated tests!"
|
257
|
+
else
|
258
|
+
puts "Found #{untested_apis.length} untested public APIs:"
|
259
|
+
puts
|
260
|
+
|
261
|
+
untested_apis.each do |api|
|
262
|
+
symbol = @data[:symbols]&.find { |s| s[:id] == api[:symbol_id] }
|
263
|
+
if symbol
|
264
|
+
puts " #{symbol[:file]}:#{symbol[:line]} - #{symbol[:name]}"
|
265
|
+
puts " Suggestion: Create characterization tests"
|
266
|
+
puts
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def show_symbols(format)
|
273
|
+
return puts "No symbols data available" unless @data[:symbols]&.any?
|
274
|
+
|
275
|
+
case format
|
276
|
+
when "json"
|
277
|
+
puts JSON.pretty_generate(@data[:symbols])
|
278
|
+
when "table"
|
279
|
+
show_symbols_table
|
280
|
+
else
|
281
|
+
show_symbols_summary
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def show_symbols_table
|
286
|
+
table = create_table(
|
287
|
+
["Type", "Name", "File", "Line", "Visibility"],
|
288
|
+
@data[:symbols].map do |symbol|
|
289
|
+
[
|
290
|
+
symbol[:kind],
|
291
|
+
symbol[:name],
|
292
|
+
symbol[:file],
|
293
|
+
symbol[:line],
|
294
|
+
symbol[:visibility]
|
295
|
+
]
|
296
|
+
end
|
297
|
+
)
|
298
|
+
|
299
|
+
puts "\nšļø Symbols"
|
300
|
+
puts "=" * 80
|
301
|
+
puts table.render
|
302
|
+
end
|
303
|
+
|
304
|
+
def show_symbols_summary
|
305
|
+
puts "\nšļø Symbols Summary"
|
306
|
+
puts "=" * 50
|
307
|
+
|
308
|
+
symbol_types = @data[:symbols].group_by { |s| s[:kind] }
|
309
|
+
|
310
|
+
symbol_types.each do |type, symbols|
|
311
|
+
puts "#{type.capitalize}: #{symbols.length}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def show_imports(format)
|
316
|
+
return puts "No imports data available" unless @data[:imports]&.any?
|
317
|
+
|
318
|
+
case format
|
319
|
+
when "json"
|
320
|
+
puts JSON.pretty_generate(@data[:imports])
|
321
|
+
when "table"
|
322
|
+
show_imports_table
|
323
|
+
else
|
324
|
+
show_imports_summary
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def show_imports_table
|
329
|
+
table = create_table(
|
330
|
+
["Type", "Target", "File", "Line"],
|
331
|
+
@data[:imports].map do |import|
|
332
|
+
[
|
333
|
+
import[:kind],
|
334
|
+
import[:target],
|
335
|
+
import[:file],
|
336
|
+
import[:line]
|
337
|
+
]
|
338
|
+
end
|
339
|
+
)
|
340
|
+
|
341
|
+
puts "\nš¦ Imports"
|
342
|
+
puts "=" * 80
|
343
|
+
puts table.render
|
344
|
+
end
|
345
|
+
|
346
|
+
def show_imports_summary
|
347
|
+
puts "\nš¦ Imports Summary"
|
348
|
+
puts "=" * 50
|
349
|
+
|
350
|
+
import_types = @data[:imports].group_by { |i| i[:kind] }
|
351
|
+
|
352
|
+
import_types.each do |type, imports|
|
353
|
+
puts "#{type.capitalize}: #{imports.length}"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def generate_import_graph(format, output)
|
358
|
+
puts "Generating import graph in #{format} format..."
|
359
|
+
|
360
|
+
case format
|
361
|
+
when "dot"
|
362
|
+
generate_dot_graph(output)
|
363
|
+
when "mermaid"
|
364
|
+
generate_mermaid_graph(output)
|
365
|
+
when "json"
|
366
|
+
generate_json_graph(output)
|
367
|
+
else
|
368
|
+
puts "Unsupported graph format: #{format}"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def generate_dot_graph(output)
|
373
|
+
content = ["digraph ImportGraph {"]
|
374
|
+
content << " rankdir=LR;"
|
375
|
+
content << " node [shape=box];"
|
376
|
+
|
377
|
+
@data[:imports]&.each do |import|
|
378
|
+
from = import[:file].gsub(/[^a-zA-Z0-9]/, "_")
|
379
|
+
to = import[:target].gsub(/[^a-zA-Z0-9]/, "_")
|
380
|
+
content << " \"#{from}\" -> \"#{to}\" [label=\"#{import[:kind]}\"];"
|
381
|
+
end
|
382
|
+
|
383
|
+
content << "}"
|
384
|
+
|
385
|
+
if output
|
386
|
+
File.write(output, content.join("\n"))
|
387
|
+
puts "Graph written to #{output}"
|
388
|
+
else
|
389
|
+
puts content.join("\n")
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def generate_mermaid_graph(output)
|
394
|
+
content = ["graph LR"]
|
395
|
+
|
396
|
+
@data[:imports]&.each do |import|
|
397
|
+
from = import[:file].gsub(/[^a-zA-Z0-9]/, "_")
|
398
|
+
to = import[:target].gsub(/[^a-zA-Z0-9]/, "_")
|
399
|
+
content << " #{from} --> #{to}"
|
400
|
+
end
|
401
|
+
|
402
|
+
if output
|
403
|
+
File.write(output, content.join("\n"))
|
404
|
+
puts "Graph written to #{output}"
|
405
|
+
else
|
406
|
+
puts content.join("\n")
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def generate_json_graph(output)
|
411
|
+
graph_data = {
|
412
|
+
nodes: [],
|
413
|
+
edges: []
|
414
|
+
}
|
415
|
+
|
416
|
+
# Add nodes
|
417
|
+
files = (@data[:imports]&.map { |i| i[:file] } || []).uniq
|
418
|
+
targets = (@data[:imports]&.map { |i| i[:target] } || []).uniq
|
419
|
+
|
420
|
+
(files + targets).uniq.each do |node|
|
421
|
+
graph_data[:nodes] << {id: node, label: node}
|
422
|
+
end
|
423
|
+
|
424
|
+
# Add edges
|
425
|
+
@data[:imports]&.each do |import|
|
426
|
+
graph_data[:edges] << {
|
427
|
+
from: import[:file],
|
428
|
+
to: import[:target],
|
429
|
+
label: import[:kind]
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
if output
|
434
|
+
File.write(output, JSON.pretty_generate(graph_data))
|
435
|
+
puts "Graph written to #{output}"
|
436
|
+
else
|
437
|
+
puts JSON.pretty_generate(graph_data)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def generate_call_graph(_format, _output)
|
442
|
+
# Similar to import graph but for method calls
|
443
|
+
puts "Call graph generation not yet implemented"
|
444
|
+
end
|
445
|
+
|
446
|
+
def generate_cycle_graph(_format, _output)
|
447
|
+
# Generate graph showing only the cycles
|
448
|
+
puts "Cycle graph generation not yet implemented"
|
449
|
+
end
|
450
|
+
|
451
|
+
def count_files
|
452
|
+
@data[:symbols]&.map { |s| s[:file] }&.uniq&.length || 0
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|