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.
- checksums.yaml +4 -4
- data/README.md +191 -5
- 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_personas.rb +1 -1
- data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
- data/lib/aidp/analyze/data_retention_manager.rb +0 -5
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -79
- data/lib/aidp/analyze/export_manager.rb +0 -7
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- 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/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/parallel_processor.rb +0 -6
- data/lib/aidp/analyze/performance_optimizer.rb +0 -3
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/repository_chunker.rb +14 -21
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +35 -30
- data/lib/aidp/analyze/storage.rb +233 -178
- data/lib/aidp/analyze/tool_configuration.rb +21 -36
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +160 -45
- data/lib/aidp/core_ext/class_attribute.rb +36 -0
- data/lib/aidp/database/pg_adapter.rb +148 -0
- data/lib/aidp/database_config.rb +69 -0
- data/lib/aidp/database_connection.rb +72 -0
- data/lib/aidp/execute/runner.rb +65 -92
- data/lib/aidp/execute/steps.rb +81 -82
- data/lib/aidp/job_manager.rb +41 -0
- data/lib/aidp/jobs/base_job.rb +45 -0
- data/lib/aidp/jobs/provider_execution_job.rb +83 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +160 -3
- data/lib/aidp/providers/base.rb +153 -6
- data/lib/aidp/providers/cursor.rb +245 -43
- data/lib/aidp/providers/gemini.rb +164 -3
- data/lib/aidp/providers/supervised_base.rb +317 -0
- data/lib/aidp/providers/supervised_cursor.rb +22 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +31 -34
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- metadata +91 -36
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
@@ -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.
|
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
|
-
#
|
307
|
+
# 4. Review the PRD
|
145
308
|
cat docs/PRD.md
|
146
309
|
# ā Edit if needed
|
147
310
|
|
148
|
-
#
|
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
|
-
#
|
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
|