aidp 0.3.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +191 -5
  3. data/lib/aidp/analysis/kb_inspector.rb +456 -0
  4. data/lib/aidp/analysis/seams.rb +188 -0
  5. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
  6. data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
  7. data/lib/aidp/analyze/agent_personas.rb +1 -1
  8. data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
  9. data/lib/aidp/analyze/data_retention_manager.rb +0 -5
  10. data/lib/aidp/analyze/database.rb +99 -82
  11. data/lib/aidp/analyze/error_handler.rb +12 -79
  12. data/lib/aidp/analyze/export_manager.rb +0 -7
  13. data/lib/aidp/analyze/focus_guidance.rb +2 -2
  14. data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
  15. data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
  16. data/lib/aidp/analyze/memory_manager.rb +34 -60
  17. data/lib/aidp/analyze/metrics_storage.rb +336 -0
  18. data/lib/aidp/analyze/parallel_processor.rb +0 -6
  19. data/lib/aidp/analyze/performance_optimizer.rb +0 -3
  20. data/lib/aidp/analyze/prioritizer.rb +2 -2
  21. data/lib/aidp/analyze/repository_chunker.rb +14 -21
  22. data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
  23. data/lib/aidp/analyze/runner.rb +107 -191
  24. data/lib/aidp/analyze/steps.rb +35 -30
  25. data/lib/aidp/analyze/storage.rb +233 -178
  26. data/lib/aidp/analyze/tool_configuration.rb +21 -36
  27. data/lib/aidp/cli/jobs_command.rb +489 -0
  28. data/lib/aidp/cli/terminal_io.rb +52 -0
  29. data/lib/aidp/cli.rb +160 -45
  30. data/lib/aidp/core_ext/class_attribute.rb +36 -0
  31. data/lib/aidp/database/pg_adapter.rb +148 -0
  32. data/lib/aidp/database_config.rb +69 -0
  33. data/lib/aidp/database_connection.rb +72 -0
  34. data/lib/aidp/execute/runner.rb +65 -92
  35. data/lib/aidp/execute/steps.rb +81 -82
  36. data/lib/aidp/job_manager.rb +41 -0
  37. data/lib/aidp/jobs/base_job.rb +45 -0
  38. data/lib/aidp/jobs/provider_execution_job.rb +83 -0
  39. data/lib/aidp/provider_manager.rb +25 -0
  40. data/lib/aidp/providers/agent_supervisor.rb +348 -0
  41. data/lib/aidp/providers/anthropic.rb +160 -3
  42. data/lib/aidp/providers/base.rb +153 -6
  43. data/lib/aidp/providers/cursor.rb +245 -43
  44. data/lib/aidp/providers/gemini.rb +164 -3
  45. data/lib/aidp/providers/supervised_base.rb +317 -0
  46. data/lib/aidp/providers/supervised_cursor.rb +22 -0
  47. data/lib/aidp/version.rb +1 -1
  48. data/lib/aidp.rb +31 -34
  49. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
  50. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  51. metadata +91 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceebdfcb742085ac8d9ea6b5db63557d5fe999f498e97699c199fc9651cf4654
4
- data.tar.gz: f1b230dcc33692c4c0391464beaf305b76993dbe02964d8c80198a7a50294a5c
3
+ metadata.gz: e32a892070257857ac578c965a7aa1bb240a1a40a58f884bc6740a5aa6a57804
4
+ data.tar.gz: cfca9c65bc0d3b5e784fbab8dcff12545982d5bb00057e575dfdfcd77007accf
5
5
  SHA512:
6
- metadata.gz: d20547fa251feb09a97e6795b04fc09dd13d0f0fd801c5abdc994bb27a20898a604692eb04b7ed8a370ac7619bcbaf89a17af60dc34c844e782e8b68087ef67a
7
- data.tar.gz: cb8503f9b0a85d6113d9ab2dada1cb0087b9b9e6ab1abb44894aa5152d3596c782c08f4708e3f03f9b969a94736acb7d882350b6f0cb869787069c8b12ddde85
6
+ metadata.gz: 99c4b48fce7af4bc8a043549fcb4ba0119dc84af870fdb8b0aae26045c4626e16cbd035df94e8b5d5c638d10a1a26f3a76b7c260647b701a55ef1f795a373ca0
7
+ data.tar.gz: 818054559441866f659d77d5b0e10721822c3aa5abc74010e90f5cbbb13a3d1fe85355fb6e2f82a867e24ed4b24e71670a72370b23c69526bf0400f7a1bc2a6a
data/README.md CHANGED
@@ -76,6 +76,7 @@ The pipeline includes 15 steps total:
76
76
  aidp status # Show progress of all steps
77
77
  aidp execute next # Run next pending step
78
78
  aidp approve current # Approve current gate step
79
+ aidp jobs # Monitor background jobs (real-time)
79
80
  aidp detect # See which AI provider will be used
80
81
  aidp execute <step> # Run specific step (e.g., prd, arch, tasks)
81
82
  aidp approve <step> # Approve specific step
@@ -97,6 +98,164 @@ AIDP_PROVIDER=anthropic aidp execute next
97
98
  AIDP_LLM_CMD=/usr/local/bin/claude aidp execute next
98
99
  ```
99
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
+
209
+ ## Background Jobs
210
+
211
+ AIDP uses background jobs to handle all AI provider executions, providing better reliability and real-time monitoring capabilities.
212
+
213
+ ### Job Monitoring
214
+
215
+ Monitor running and completed jobs in real-time:
216
+
217
+ ```bash
218
+ aidp jobs # Show job status with real-time updates
219
+ ```
220
+
221
+ The jobs view displays:
222
+
223
+ - **Running jobs** with live progress updates
224
+ - **Queued jobs** waiting to be processed
225
+ - **Completed jobs** with execution results
226
+ - **Failed jobs** with error details
227
+
228
+ ### Job Controls
229
+
230
+ From the jobs view, you can:
231
+
232
+ - **Retry failed jobs** by pressing `r` on a failed job
233
+ - **View job details** by pressing `d` on any job
234
+ - **Exit monitoring** by pressing `q`
235
+
236
+ ### Job Persistence
237
+
238
+ - Jobs persist across CLI restarts
239
+ - Job history is preserved for analysis
240
+ - Failed jobs can be retried at any time
241
+ - All job metadata and logs are stored
242
+
243
+ ### Database Setup
244
+
245
+ AIDP uses PostgreSQL for job management. Ensure PostgreSQL is installed and running:
246
+
247
+ ```bash
248
+ # macOS (using Homebrew)
249
+ brew install postgresql
250
+ brew services start postgresql
251
+
252
+ # Ubuntu/Debian
253
+ sudo apt-get install postgresql postgresql-contrib
254
+ sudo systemctl start postgresql
255
+
256
+ # The database will be created automatically on first use
257
+ ```
258
+
100
259
  ## File-Based Interaction
101
260
 
102
261
  At gate steps, the AI creates files for interaction instead of requiring real-time chat:
@@ -137,20 +296,24 @@ Here's a typical session:
137
296
  aidp execute next
138
297
  # → Creates docs/PRD.md and PRD_QUESTIONS.md
139
298
 
140
- # 2. Review the questions (if any)
299
+ # 2. Monitor job progress (optional)
300
+ aidp jobs
301
+ # → Shows real-time job status and progress
302
+
303
+ # 3. Review the questions (if any)
141
304
  cat PRD_QUESTIONS.md
142
305
  # → If questions exist, edit the file with your answers, then re-run
143
306
 
144
- # 3. Review the PRD
307
+ # 4. Review the PRD
145
308
  cat docs/PRD.md
146
309
  # → Edit if needed
147
310
 
148
- # 4. Approve and continue
311
+ # 5. Approve and continue
149
312
  aidp approve current
150
313
  aidp execute next
151
314
  # → Creates docs/NFRs.md automatically
152
315
 
153
- # 5. Continue through gates
316
+ # 6. Continue through gates
154
317
  aidp execute next
155
318
  # → Creates docs/Architecture.md and ARCH_QUESTIONS.md
156
319
  # → Repeat review/approve cycle
@@ -162,9 +325,19 @@ aidp execute next
162
325
  # Install dependencies
163
326
  bundle install
164
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
+
165
334
  # Run tests
166
335
  bundle exec rspec
167
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
+
168
341
  # Run linter
169
342
  bundle exec standardrb
170
343
 
@@ -175,6 +348,19 @@ bundle exec standardrb --fix
175
348
  bundle exec rake build
176
349
  ```
177
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
+
178
364
  ## Contributing
179
365
 
180
366
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and conventional commit guidelines.
@@ -199,7 +385,7 @@ The gem automates a complete 15-step development pipeline:
199
385
  - **Threat Model** → Security analysis (`docs/ThreatModel.md`)
200
386
  - **Test Plan** → Testing strategy (`docs/TestPlan.md`)
201
387
  - **Scaffolding** → Project structure guidance (`docs/ScaffoldingGuide.md`)
202
- - **Static Analysis** → Code quality tools (`docs/StaticAnalysis.md`)
388
+ - **Static Analysis** → Code quality tools and Tree-sitter analysis (`docs/StaticAnalysis.md`, `.aidp/kb/`)
203
389
  - **Observability** → Monitoring and SLOs (`docs/Observability.md`)
204
390
  - **Delivery** → Deployment strategy (`docs/DeliveryPlan.md`)
205
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