# Dependency Tracking in Reflex
**Status:** Specification (Not Yet Implemented)
**Version:** 1.0
**Last Updated:** 2025-11-11
## Table of Contents
1. [Overview](#overview)
2. [Motivation](#motivation)
3. [Architecture](#architecture)
4. [Storage Schema](#storage-schema)
5. [Command Interface](#command-interface)
6. [Language Support](#language-support)
7. [Implementation Phases](#implementation-phases)
8. [Performance Characteristics](#performance-characteristics)
9. [Use Cases & Examples](#use-cases--examples)
10. [Future Enhancements](#future-enhancements)
---
## Overview
Reflex will support **file dependency tracking** to help developers and AI coding agents understand relationships between files in a codebase. This feature enables:
- **Impact Analysis**: "What breaks if I change this file?"
- **Context Understanding**: "What does this code depend on?"
- **Reverse Lookup**: "What uses this file/module?"
- **Architecture Analysis**: Finding circular dependencies, hotspots, and orphaned files
### Key Design Principles
1. **Index Shallow, Traverse Deep**: Store only direct (depth-1) dependencies; compute deeper relationships on demand
2. **Dual Command Approach**: Separate search augmentation (`rfx query --dependencies`) from graph analysis (`rfx deps`)
3. **Lazy Evaluation**: Compute expensive operations only when explicitly requested
4. **Cross-Language Consistency**: Single API works across all 18 supported languages
5. **AI-First Design**: Structured output optimized for AI coding agents
---
## Motivation
### Problem: AI Agents Need Dependency Context
When AI coding agents work with code, they need to understand:
- What a file imports (to understand its context)
- What imports a file (to assess change impact)
- Whether code is safe to delete (no incoming dependencies)
- How modules are interconnected (architectural understanding)
### Why Not Use grep/awk?
Traditional tools have limitations:
- **Slow**: Full codebase scan on every query (1-5 seconds)
- **Inaccurate**: False positives from comments, strings, logs
- **Language-Specific**: Different regex per language (error-prone)
- **No Resolution**: Returns raw import strings, not resolved paths
- **Manual Parsing**: Agent must parse and classify results
### Reflex Advantages
- **Fast**: Indexed lookups in <5ms (10-100x faster than grep)
- **Accurate**: Tree-sitter parsing eliminates false positives
- **Universal**: Same API across all 18 languages
- **Resolved**: Import paths resolved to actual files
- **Structured**: Clean JSON output, no manual parsing
---
## Architecture
### Core Concept: Depth-1 Storage
Reflex stores **only direct dependencies** (depth-1) in the database. Deeper relationships are computed on-demand by traversing the index.
```
Example Codebase:
file_a.rs → [file_b.rs, file_c.rs] (stored)
file_b.rs → [file_d.rs, file_e.rs] (stored)
file_d.rs → [file_f.rs] (stored)
Depth-3 Query from file_a.rs:
1. Lookup file_a.rs → get [file_b.rs, file_c.rs]
2. Lookup file_b.rs → get [file_d.rs, file_e.rs]
3. Lookup file_d.rs → get [file_f.rs]
Result: 6 lookups, all indexed (O(1) each)
```
### Why This Works
- **Storage**: O(n) instead of O(n²) for full closure
- **Updates**: Change one file, update one row
- **Flexibility**: Compute any depth on-demand
- **Performance**: Most files have 5-20 deps, not hundreds
### Component Integration
```
Existing Components:
- Indexer: Parses files with tree-sitter
- CacheManager: Manages SQLite database
- QueryEngine: Executes searches
New Components:
- DependencyExtractor: Extract imports from AST
- DependencyIndex: Store/retrieve dependency relationships
- DependencyTraverser: Compute transitive closures
- GraphAnalyzer: Detect cycles, hotspots, islands
```
---
## Storage Schema
### Database Tables
#### file_dependencies Table
```sql
CREATE TABLE file_dependencies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_id INTEGER NOT NULL, -- Source file (FK to files.id)
imported_path TEXT NOT NULL, -- Import as written in source
resolved_file_id INTEGER, -- Target file (FK to files.id), NULL if external
import_type TEXT NOT NULL, -- 'internal', 'external', 'stdlib'
line_number INTEGER NOT NULL, -- Line where import appears
imported_symbols TEXT, -- JSON array of specific symbols (if selective import)
FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE,
FOREIGN KEY (resolved_file_id) REFERENCES files(id) ON DELETE SET NULL
);
CREATE INDEX idx_deps_file ON file_dependencies(file_id);
CREATE INDEX idx_deps_resolved ON file_dependencies(resolved_file_id);
CREATE INDEX idx_deps_type ON file_dependencies(import_type);
```
### Import Types
- **internal**: Import references another file in the project
- **external**: Import from external package/library (node_modules, pip, cargo, etc.)
- **stdlib**: Standard library import (depends on language)
### Example Data
```sql
-- Rust: use std::collections::HashMap;
INSERT INTO file_dependencies (file_id, imported_path, resolved_file_id, import_type, line_number, imported_symbols)
VALUES (42, 'std::collections::HashMap', NULL, 'stdlib', 5, '["HashMap"]');
-- Rust: use crate::models::User;
INSERT INTO file_dependencies (file_id, imported_path, resolved_file_id, import_type, line_number, imported_symbols)
VALUES (42, 'crate::models::User', 15, 'internal', 7, '["User"]');
-- Python: import requests
INSERT INTO file_dependencies (file_id, imported_path, resolved_file_id, import_type, line_number, imported_symbols)
VALUES (55, 'requests', NULL, 'external', 3, NULL);
-- Python: from .utils import format_date
INSERT INTO file_dependencies (file_id, imported_path, resolved_file_id, import_type, line_number, imported_symbols)
VALUES (55, '.utils', 62, 'internal', 5, '["format_date"]');
```
---
## Command Interface
### Dual-Command Approach
Reflex provides **two separate commands** for different use cases:
#### 1. `rfx query --dependencies` (Search Augmentation)
Add dependency context to search results.
```bash
# Find all API-related code and see what it depends on
rfx query "ApiClient" --dependencies
# Output: Each search result includes immediate dependencies
{
"path": "src/api/client.rs",
"symbol": "ApiClient",
"span": { "start_line": 15, "end_line": 45 },
"dependencies": [
{"path": "reqwest", "type": "external"},
{"path": "src/config.rs", "type": "internal"},
{"path": "src/models/user.rs", "type": "internal"}
]
}
```
**Characteristics:**
- Operates on search results (10-100 matches)
- Depth-1 only (immediate dependencies)
- Simple list format
- Answers: "What does this code need?"
- **Use case:** AI agents understanding code context
#### 2. `rfx deps` (Graph Analysis)
Dedicated command for dependency analysis and graph operations.
**Single File Operations:**
```bash
# Show dependency tree
rfx deps src/main.rs
# Show who depends on this file (reverse lookup)
rfx deps src/config.rs --reverse
# Deep traversal (compute transitive closure)
rfx deps src/api.rs --depth 3
# Show as tree visualization
rfx deps src/main.rs --tree
```
**Graph-Wide Operations:**
```bash
# Find circular dependencies
rfx deps --circular
# Find most imported files (hotspots)
rfx deps --hotspots
# Find files that nothing imports (orphans)
rfx deps --unused
# Find disconnected components
rfx deps --islands
# Full analysis report
rfx deps --analyze
```
**Output Formats:**
```bash
# JSON (default, for programmatic use)
rfx deps src/main.rs --json
# Tree visualization (for terminal)
rfx deps src/main.rs --tree
# Graphviz DOT format (for visualization tools)
rfx deps src/main.rs --format dot > deps.dot
dot -Tpng deps.dot -o deps.png
```
**Characteristics:**
- Operates on specific files or entire graph
- Any depth (traversal-based)
- Rich output formats
- Graph algorithms (cycles, hotspots, etc.)
- **Use case:** Architecture analysis, refactoring planning
---
## Language Support
Reflex will extract dependencies for all 18 supported languages using Tree-sitter.
### Import/Dependency Syntax by Language
#### Rust
```rust
// Module imports
use std::collections::HashMap; // stdlib
use crate::models::User; // internal (absolute)
use super::utils; // internal (relative)
use self::nested::Module; // internal (self-relative)
mod my_module; // module declaration
extern crate serde; // external crate (Rust 2015)
// Tree-sitter patterns:
// - use_declaration
// - mod_item
// - extern_crate_declaration
```
#### Python
```python
import os # stdlib
import requests # external
from typing import List, Optional # stdlib
from .utils import format_date # internal (relative)
from ..models import User # internal (parent)
from mypackage.module import func # internal (absolute)
# Tree-sitter patterns:
# - import_statement
# - import_from_statement
```
#### JavaScript / TypeScript
```javascript
import React from 'react'; // external
import { useState } from 'react'; // external (named)
import * as utils from './utils'; // internal
import type { User } from './types'; // TS type import
const fs = require('fs'); // CommonJS (stdlib)
const express = require('express'); // CommonJS (external)
// Tree-sitter patterns:
// - import_statement
// - import_clause
// - call_expression (for require)
```
#### Go
```go
import "fmt" // stdlib
import "github.com/user/repo/pkg" // external
import . "math" // dot import
import _ "database/sql" // blank import
import (
"context"
"myapp/internal/models" // internal
)
// Tree-sitter patterns:
// - import_declaration
// - import_spec
```
#### Java
```java
import java.util.List; // stdlib
import java.util.*; // wildcard
import com.example.myapp.User; // internal
import org.springframework.boot.*; // external
// Tree-sitter patterns:
// - import_declaration
```
#### C / C++
```c
#include <stdio.h> // stdlib (angle brackets)
#include "my_header.h" // internal (quotes)
#include <vector> // stdlib (C++)
#include "../common/utils.h" // internal (relative)
// Tree-sitter patterns:
// - preproc_include
```
#### C#
```csharp
using System; // stdlib
using System.Collections.Generic; // stdlib
using MyApp.Models; // internal
using static System.Math; // static using
// Tree-sitter patterns:
// - using_directive
```
#### PHP
```php
<?php
use PDO; // stdlib
use Symfony\Component\HttpFoundation\Request; // external
use App\Models\User; // internal
require_once 'config.php'; // include (internal)
include __DIR__ . '/utils.php'; // include (internal)
// Tree-sitter patterns:
// - namespace_use_declaration
// - require_expression
// - include_expression
```
#### Ruby
```ruby
require 'json' # stdlib
require 'rails' # external (gem)
require_relative '../lib/utils' # internal (relative)
load 'config.rb' # load (internal)
# Tree-sitter patterns:
# - call (require/require_relative/load)
```
#### Kotlin
```kotlin
import java.util.Date // stdlib
import kotlin.collections.* // stdlib (wildcard)
import com.example.myapp.User // internal
import androidx.appcompat.app.AppCompatActivity // external
// Tree-sitter patterns:
// - import_header
```
#### Zig
```zig
const std = @import("std"); // stdlib
const utils = @import("utils.zig"); // internal
const lib = @import("external_lib"); // external
// Tree-sitter patterns:
// - call_expression (@import)
```
#### Vue
```vue
<script>
import { ref } from 'vue' import MyComponent from './MyComponent.vue' import { api } from '@/services/api' </script>
<script setup lang="ts">
import type { User } from '@/types' </script>
// Parsing: Extract from <script> blocks using line-based parsing
```
#### Svelte
```svelte
<script>
import { onMount } from 'svelte' // external
import Component from './Component.svelte' // internal
import { store } from '../stores' // internal
</script>
// Parsing: Extract from <script> blocks using line-based parsing
```
### Path Resolution Strategies
#### Simple (No Resolution Needed)
- **Go**: Import paths are fully qualified
- **Java**: Package names are absolute
- **Kotlin**: Import paths are absolute
#### Relative Path Resolution
- **JavaScript/TypeScript**: Resolve `./`, `../`, `@/` aliases
- **Python**: Resolve relative imports (`.`, `..`)
- **Rust**: Resolve `super::`, `self::`, `crate::`
- **C/C++**: Resolve `#include` with header search paths
#### Package Manager Integration
- **JavaScript/TypeScript**: Check `node_modules/`, `package.json`
- **Python**: Check virtual environment, `site-packages`
- **Rust**: Check `Cargo.toml` for workspace members
- **Ruby**: Check `Gem` paths
- **PHP**: Check `vendor/` (Composer)
---
## Implementation Phases
### Phase 1: Core Infrastructure (P1 - High Priority)
**Goal:** Basic dependency extraction and storage
**Tasks:**
1. Add `file_dependencies` table to SQLite schema (src/cache.rs)
2. Create `DependencyExtractor` trait (src/parsers/mod.rs)
3. Implement `DependencyIndex` for storage/retrieval (src/dependency.rs)
4. Add `--dependencies` flag to `rfx query` command
5. Extend `SearchResult` model to include dependencies
**Deliverables:**
- `rfx query "pattern" --dependencies` works for at least one language (Rust)
- Dependencies stored in SQLite
- JSON output includes dependency list
**Estimated Time:** 3-5 days
### Phase 2: Language Parser Updates (P1 - High Priority)
**Goal:** Extract imports for all 18 languages
**Tier 1 - Simple Syntax (1-2 days each):**
1. **Rust**: `use`, `mod`, `extern crate`
2. **Python**: `import`, `from...import`
3. **JavaScript/TypeScript**: `import`, `require`
4. **Go**: `import`
5. **Java**: `import`
6. **C#**: `using`
**Tier 2 - Path Resolution (2-3 days each):**
7. **Ruby**: `require`, `require_relative`
8. **PHP**: `use`, `require`, `include`
9. **C/C++**: `#include` with search paths
10. **Kotlin**: `import`
**Tier 3 - Framework-Specific (2-3 days each):**
11. **Vue**: Component imports in `<script>` blocks
12. **Svelte**: Component imports
13. **Zig**: `@import`
**Deliverables:**
- Each language parser extracts import statements
- Tree-sitter queries identify import nodes
- Basic path classification (internal vs external)
- Unit tests for each parser
**Estimated Time:** 12-15 days total
### Phase 3: Query Engine Integration (P1 - High Priority)
**Goal:** Augment search results with dependency info
**Tasks:**
1. Add dependency loading to `QueryEngine`
2. Implement `enrich_with_dependencies()` for results
3. Optimize queries with indexes
**Deliverables:**
- `rfx query "pattern" --dependencies` returns enriched results
- Fast indexed lookups (<5ms overhead per result)
**Estimated Time:** 2-3 days
### Phase 4: Dependency Command (P2 - Medium Priority)
**Goal:** Dedicated `rfx deps` command for graph analysis
**Tasks:**
1. Create `rfx deps` CLI command (src/cli.rs)
2. Implement single-file operations:
- `rfx deps <file>` (show dependencies)
- `rfx deps <file> --reverse` (show dependents)
- `rfx deps <file> --depth N` (traversal)
3. Implement tree visualization format
4. Add JSON output for programmatic use
**Deliverables:**
- `rfx deps src/main.rs` shows dependency tree
- `rfx deps src/config.rs --reverse` shows all importers
- `rfx deps src/api.rs --depth 3` traverses correctly
- Tree format renders in terminal
**Estimated Time:** 3-4 days
### Phase 5: Graph Algorithms (P2 - Medium Priority)
**Goal:** Advanced graph analysis features
**Tasks:**
1. Implement circular dependency detection (DFS-based)
2. Implement hotspot detection (most imported files)
3. Implement unused file detection (no incoming edges)
4. Implement island detection (connected components)
5. Create `--analyze` flag (run all analyses)
**Deliverables:**
- `rfx deps --circular` finds cycles
- `rfx deps --hotspots` ranks by import count
- `rfx deps --unused` finds orphaned files
- `rfx deps --islands` identifies components
- `rfx deps --analyze` generates comprehensive report
**Estimated Time:** 3-4 days
### Phase 6: Advanced Features (P3 - Low Priority)
**Goal:** Visualization and advanced path resolution
**Tasks:**
1. Graphviz DOT format output (`--format dot`)
2. Advanced path resolution (package managers)
3. Workspace/monorepo support
4. Selective symbol imports tracking
5. Performance optimizations (session caching)
**Deliverables:**
- `rfx deps src/main.rs --format dot | dot -Tpng > graph.png`
- Accurate resolution for complex projects
- Sub-millisecond repeated queries (cached)
**Estimated Time:** 5-7 days
**Total Estimated Time:** 28-38 days (5-7 weeks)
---
## Performance Characteristics
### Storage Overhead
**Per 10k-file project:**
- Average 10 imports per file = 100k dependency entries
- Each entry: ~100 bytes (path, type, line, symbols)
- **Total:** ~10MB in SQLite
**Compared to:**
- Trigram index: ~50MB
- Content store: ~200MB
- **Dependency storage:** 5% overhead
### Indexing Impact
**Without dependency extraction:**
- Parse file with tree-sitter: 5ms per file
- Extract symbols: 2ms per file
- **Total:** 7ms per file
**With dependency extraction:**
- Parse file with tree-sitter: 5ms per file (same)
- Extract symbols: 2ms per file (same)
- Extract imports: 1ms per file (additional)
- **Total:** 8ms per file
**Impact:** ~15% slower indexing (acceptable tradeoff)
### Query Performance
#### rfx query --dependencies
```
Base query: 2-5ms (trigram search)
Enrich with deps: +0.5-2ms per result (indexed lookup)
Total: 3-7ms for 10 results
```
#### rfx deps <file>
```
Depth 1: 1-2ms (single indexed query)
Depth 2: 5-15ms (recursive lookups, ~10 files)
Depth 3: 10-30ms (exponential, ~50 files)
```
#### rfx deps --circular
```
Full graph traversal: 50-200ms (10k files)
DFS-based cycle detection
Worst case: O(V + E) where V = files, E = imports
```
#### rfx deps --hotspots
```
SQL COUNT(*) GROUP BY: 10-50ms
Already indexed, very fast
```
### Comparison to grep
**Reverse dependency lookup:**
```
grep -r "config.rs" src/ # Full scan
→ 1-5 seconds (10k files)
→ Many false positives
rfx deps src/config.rs --reverse # Indexed
→ 2-5ms (exact matches only)
→ No false positives
```
**Speedup:** 200-1000x faster than grep
---
## Use Cases & Examples
### Use Case 1: Safe Refactoring
**Scenario:** Rename `User` interface to `UserProfile`
**Without deps:**
```bash
# Agent searches and renames blindly
rfx query "interface User"
# Might miss indirect usages, breaks 30 files
```
**With deps:**
```bash
# Agent checks impact first
rfx query "interface User" --dependencies
# Sees imported by: auth.ts, api.ts, profile.ts, etc. (30 files)
rfx deps src/models/user.ts --reverse
# Gets complete list of all importers
# Makes informed decision about scope
```
### Use Case 2: Understanding Unfamiliar Code
**Scenario:** "How does authentication work?"
**Without deps:**
```bash
# Agent reads each file blindly
rfx query "auth" --symbols
# Finds 20 functions, reads all
```
**With deps:**
```bash
# Agent understands the stack immediately
rfx query "authenticateUser" --dependencies --json
{
"symbol": "authenticateUser",
"dependencies": [
{"path": "jsonwebtoken", "type": "external"},
{"path": "src/models/user.ts", "type": "internal"},
{"path": "src/cache/redis.ts", "type": "internal"}
]
}
# "Ah, JWT tokens stored in Redis with User model"
```
### Use Case 3: Dead Code Detection
**Scenario:** "Can I delete this old module?"
```bash
rfx deps src/legacy/old-api.ts --reverse
# Returns: No files import this
# Safe to delete!
```
### Use Case 4: Architecture Audit
**Scenario:** Check for problematic patterns
```bash
# Find circular dependencies
rfx deps --circular
→ Found 2 cycles:
src/user.ts ↔ src/profile.ts
src/api.ts → src/db.ts → src/models.ts → src/api.ts
# Find hotspots (files with too many dependents)
rfx deps --hotspots --limit 10
→ src/config.ts (47 imports) ⚠️ God object
src/utils/format.ts (33 imports)
src/types.ts (28 imports)
# Find unused files
rfx deps --unused
→ 5 orphaned files:
src/old/unused.ts
src/temp/test.ts
...
```
### Use Case 5: Debugging Import Errors
**Scenario:** Build failing due to import error
```bash
# Find what imports the problematic module
rfx query "UserService" --dependencies
→ Shows: imports './models/User'
→ But User.ts was renamed to UserModel.ts
→ "Found the issue!"
```
### Use Case 6: Find Usage Examples
**Scenario:** "How do I use the Logger class?"
```bash
# Find all files that import Logger
rfx deps src/logger.ts --reverse --limit 5
→ Returns 5 example files
→ Agent reads them to understand usage patterns
```
---
## Future Enhancements
### Phase 7: Potential Additions (Not Planned)
#### Call Graph (Limited)
Track function calls within files for impact analysis.
- **Complexity:** High (requires full AST traversal)
- **Value:** Medium (most impact already covered by file-level deps)
- **Verdict:** Defer until user demand
#### Monorepo / Workspace Support
Handle multi-package repositories (npm workspaces, Cargo workspaces).
- **Complexity:** Medium (resolve cross-package imports)
- **Value:** High for monorepo users
- **Verdict:** Consider for Phase 6
#### Dependency Change Tracking
Show dependency diffs between git branches.
- **Complexity:** Medium (requires git integration)
- **Value:** Medium (nice for PRs)
- **Verdict:** Future enhancement
#### Interactive Dependency Explorer (TUI)
Terminal UI for browsing dependency graph.
- **Complexity:** Medium (ratatui integration)
- **Value:** Medium (exploratory analysis)
- **Verdict:** Aligns with existing Interactive Mode TODO
---
## Testing Strategy
### Unit Tests
**Per-Language Parser Tests:**
- Extract imports correctly
- Handle multiline imports
- Classify internal vs external
- Resolve relative paths
- Edge cases (comments, strings, etc.)
**Dependency Index Tests:**
- Store and retrieve dependencies
- Handle cycles gracefully
- Performance (indexed lookups <5ms)
**Graph Algorithm Tests:**
- Circular dependency detection
- Transitive closure computation
- Hotspot ranking
- Island detection
### Integration Tests
**End-to-End Workflows:**
1. Index project with deps → Query with --dependencies → Verify results
2. Index project → `rfx deps` traversal → Verify depth correctness
3. Index project → Modify file → Incremental reindex → Verify dep updates
4. Multi-language project → Verify cross-language deps work
**Performance Tests:**
- Indexing overhead: <20% slowdown
- Query enrichment: <2ms per result
- Reverse lookup: <5ms for 50 dependents
- Graph traversal: <200ms for 10k files
### Real-World Validation
Test on actual projects:
- **Reflex itself** (Rust, ~5k LOC)
- **Next.js project** (TypeScript/JavaScript, ~10k LOC)
- **Django project** (Python, ~15k LOC)
- **Large monorepo** (Mixed languages, ~50k LOC)
---
## Implementation Checklist
### Phase 1: Core Infrastructure
- [ ] Add `file_dependencies` table to schema
- [ ] Create `DependencyExtractor` trait
- [ ] Implement `DependencyIndex` struct
- [ ] Add `--dependencies` flag to `rfx query`
- [ ] Extend `SearchResult` with dependencies field
- [ ] Tests: Basic storage and retrieval
### Phase 2: Language Parsers (18 languages)
- [ ] Rust: `use`, `mod`, `extern crate`
- [ ] Python: `import`, `from...import`
- [ ] JavaScript: `import`, `require`
- [ ] TypeScript: `import`, `require`, type imports
- [ ] Go: `import`
- [ ] Java: `import`
- [ ] C: `#include`
- [ ] C++: `#include`
- [ ] C#: `using`
- [ ] PHP: `use`, `require`, `include`
- [ ] Ruby: `require`, `require_relative`
- [ ] Kotlin: `import`
- [ ] Zig: `@import`
- [ ] Vue: Extract from `<script>` blocks
- [ ] Svelte: Extract from `<script>` blocks
- [ ] Tests: Per-language import extraction
### Phase 3: Query Integration
- [ ] Load dependencies in `QueryEngine`
- [ ] Implement `enrich_with_dependencies()`
- [ ] Add `--only-internal` filter
- [ ] Add `--only-external` filter
- [ ] Add `--imported-by` flag
- [ ] Tests: Query enrichment correctness
### Phase 4: Dependency Command
- [ ] Create `rfx deps` CLI command
- [ ] Implement `rfx deps <file>`
- [ ] Implement `rfx deps <file> --reverse`
- [ ] Implement `rfx deps <file> --depth N`
- [ ] Implement `--tree` format
- [ ] Implement `--json` format
- [ ] Tests: Single-file operations
### Phase 5: Graph Algorithms
- [ ] Implement circular dependency detection
- [ ] Implement `rfx deps --circular`
- [ ] Implement `rfx deps --hotspots`
- [ ] Implement `rfx deps --unused`
- [ ] Implement `rfx deps --islands`
- [ ] Implement `rfx deps --analyze`
- [ ] Tests: Graph algorithms correctness
### Phase 6: Advanced Features
- [ ] Implement `--format dot` (Graphviz)
- [ ] Advanced path resolution (node_modules, etc.)
- [ ] Session caching for repeated queries
- [ ] Workspace/monorepo support
- [ ] Tests: Advanced features
---
## Conclusion
Dependency tracking is a **high-value addition** to Reflex that significantly enhances its usefulness for AI coding agents and human developers. The depth-1 storage model with on-demand traversal provides an optimal balance of:
- **Performance**: Fast indexed lookups with minimal storage overhead
- **Flexibility**: Compute any depth without re-indexing
- **Simplicity**: Clean architecture, easy to maintain
- **Completeness**: Works across all 18 supported languages
**Recommended Implementation Path:**
1. Start with Phase 1 (Core Infrastructure) for quick wins
2. Roll out Phase 2 (Language Parsers) incrementally
3. Ship Phase 3 (Query Integration) for AI agent value
4. Consider Phase 4-5 (Advanced Analysis) based on user feedback
**Total Effort:** 5-7 weeks for complete implementation (all phases)
**Expected Impact:** 10-100x faster dependency lookups compared to grep, with accurate results and structured output optimized for AI agents.
---
## IMPLEMENTATION PLAN (2025-11-12)
### Current Status Assessment
#### ✅ COMPLETE (100%)
- **Database schema** (`file_dependencies` table) - fully implemented in `src/cache.rs`
- **Dependency storage** (`DependencyIndex`) - complete in `src/dependency.rs` (963 lines)
- **Graph algorithms** - all core algorithms implemented:
- Direct dependencies: `get_dependencies(file_id)`
- Reverse lookup: `get_dependents(file_id)`
- Transitive deps: `get_transitive_deps(file_id, max_depth)` (BFS)
- Circular detection: `detect_circular_dependencies()` (DFS)
- Hotspot detection: `find_hotspots(limit)`
- Unused files: `find_unused_files()`
- Path resolution: `get_file_paths()`, `get_file_path()`
- Batch operations: `batch_insert_dependencies()`, `clear_dependencies()`
- **Import extraction** - working for all languages:
- Rust: `src/parsers/rust.rs::extract_rust_imports()`
- Python: `src/parsers/python.rs::extract_python_imports()`
- TypeScript/JavaScript: `src/parsers/typescript.rs::extract_typescript_imports()`
- Go: `src/parsers/go.rs::extract_go_imports()`
- Java: `src/parsers/java.rs::extract_java_imports()`
- Kotlin: `src/parsers/kotlin.rs::extract_kotlin_imports()`
- Zig: `src/parsers/zig.rs::extract_zig_imports()`
- Vue: `src/parsers/vue.rs::extract_vue_imports()`
- Svelte: `src/parsers/svelte.rs::extract_svelte_imports()`
- **Dependency classification** - working for all languages:
- Internal/External/Stdlib classification
- Package-based reclassification (Rust, Python, Java, Kotlin, Ruby)
- Indexer integration in `src/indexer.rs`
- **Query integration** - `--dependencies` flag implemented:
- CLI support: `rfx query "pattern" --dependencies`
- HTTP API support: `GET /query?dependencies=true`
- MCP support (via query filter)
#### ❌ MISSING (0%)
- **`rfx deps` CLI command** - NOT in `src/cli.rs::Command` enum
- **Output formatters** - no tree/DOT/table formatters for deps
- **Command handlers** - no `handle_deps()` function
- **Testing** - no integration tests for full workflow
---
### Implementation Strategy
#### Phase 1: Basic `rfx deps` Command (MVP)
**Goal:** Get `rfx deps <file>` working with JSON output
**Tasks:**
1. Add `Deps` variant to `Command` enum in `src/cli.rs`
2. Create `handle_deps()` function
3. Implement basic file operations:
- `rfx deps <file>` - show direct dependencies
- `rfx deps <file> --reverse` - show dependents
- `rfx deps <file> --depth N` - transitive traversal
4. JSON output only (reuse existing models)
**Files to modify:**
- `src/cli.rs` (lines 34-297: Command enum, lines 299-346: execute() match)
**Estimated time:** 2-3 hours
#### Phase 2: Output Formatters
**Goal:** Add tree, table, and DOT format output
**Tasks:**
1. Create `src/dependency_formatter.rs` module
2. Implement formatters:
- `format_tree()` - tree visualization with Unicode box drawing
- `format_table()` - tabular output for hotspots/unused
- `format_dot()` - Graphviz DOT format
3. Add `--tree` and `--format` flags to deps command
4. Pretty-print for human readability
**New file:**
- `src/dependency_formatter.rs` (~300 lines)
**Files to modify:**
- `src/cli.rs` (add format flags)
- `src/lib.rs` (add `pub mod dependency_formatter;`)
**Estimated time:** 3-4 hours
#### Phase 3: Graph-Wide Analysis Commands
**Goal:** Implement `--circular`, `--hotspots`, `--unused`, `--analyze`
**Tasks:**
1. Extend `handle_deps()` with graph-wide operations
2. Implement subcommands:
- `rfx deps --circular` - detect and display cycles
- `rfx deps --hotspots` - show most-imported files
- `rfx deps --unused` - find orphaned files
- `rfx deps --analyze` - comprehensive report
3. Add `--limit` flag for controlling result count
4. Pretty formatting for each operation
**Files to modify:**
- `src/cli.rs` (add graph-wide flags)
**Estimated time:** 2-3 hours
#### Phase 4: Testing & Documentation
**Goal:** Ensure correctness and usability
**Tasks:**
1. Add integration tests in `tests/deps_test.rs`
2. Test on real codebases (Reflex itself, test projects)
3. Update CLAUDE.md with `rfx deps` examples
4. Add `--help` examples to CLI
**New file:**
- `tests/deps_test.rs` (~200 lines)
**Files to modify:**
- `CLAUDE.md` (add deps command documentation)
- `src/cli.rs` (improve help text)
**Estimated time:** 2-3 hours
---
### Detailed Implementation Specification
#### 1. CLI Command Structure (`src/cli.rs`)
**Add to Command enum (after Line 289):**
```rust
Deps {
file: Option<PathBuf>,
#[arg(short, long)]
reverse: bool,
#[arg(short, long, default_value = "1")]
depth: usize,
#[arg(short = 'f', long, default_value = "json")]
format: String,
#[arg(long)]
pretty: bool,
#[arg(long)]
only_internal: bool,
#[arg(long)]
only_external: bool,
#[arg(long)]
only_stdlib: bool,
#[arg(long, conflicts_with = "file")]
circular: bool,
#[arg(long, conflicts_with = "file")]
hotspots: bool,
#[arg(long, conflicts_with = "file")]
unused: bool,
#[arg(long, conflicts_with = "file")]
islands: bool,
#[arg(long, conflicts_with = "file")]
analyze: bool,
#[arg(short = 'n', long)]
limit: Option<usize>,
}
```
**Add to execute() match (after Line 342):**
```rust
Some(Command::Deps { file, reverse, depth, format, pretty, only_internal, only_external, only_stdlib, circular, hotspots, unused, islands, analyze, limit }) => {
handle_deps(file, reverse, depth, format, pretty, only_internal, only_external, only_stdlib, circular, hotspots, unused, islands, analyze, limit)
}
```
#### 2. Command Handler (`src/cli.rs`, after Line 1427)
**Add handler function:**
```rust
fn handle_deps(
file: Option<PathBuf>,
reverse: bool,
depth: usize,
format: String,
pretty_json: bool,
only_internal: bool,
only_external: bool,
only_stdlib: bool,
circular: bool,
hotspots: bool,
unused: bool,
islands: bool,
analyze: bool,
limit: Option<usize>,
) -> Result<()> {
use crate::dependency::DependencyIndex;
use crate::models::ImportType;
log::info!("Starting deps command");
let cache = CacheManager::new(".");
if !cache.exists() {
anyhow::bail!(
"No index found in current directory.\n\
\n\
Run 'rfx index' to build the code search index first.\n\
\n\
Example:\n\
$ rfx index # Index current directory\n\
$ rfx deps <file> # Analyze dependencies"
);
}
let deps_index = DependencyIndex::new(cache);
if analyze {
return handle_deps_analyze(&deps_index, &format, pretty_json, limit);
} else if circular {
return handle_deps_circular(&deps_index, &format, pretty_json);
} else if hotspots {
return handle_deps_hotspots(&deps_index, &format, pretty_json, limit);
} else if unused {
return handle_deps_unused(&deps_index, &format, pretty_json, limit);
} else if islands {
return handle_deps_islands(&deps_index, &format, pretty_json);
}
let file_path = file.ok_or_else(|| {
anyhow::anyhow!(
"No file specified.\n\
\n\
Usage:\n\
$ rfx deps <file> # Show dependencies\n\
$ rfx deps <file> --reverse # Show dependents\n\
$ rfx deps --circular # Find cycles\n\
$ rfx deps --hotspots # Most-imported files"
)
})?;
let file_str = file_path.to_string_lossy().to_string();
let file_id = deps_index.get_file_id_by_path(&file_str)?
.ok_or_else(|| anyhow::anyhow!("File '{}' not found in index", file_str))?;
let import_filter = move |import_type: &ImportType| -> bool {
if only_internal && *import_type != ImportType::Internal {
return false;
}
if only_external && *import_type != ImportType::External {
return false;
}
if only_stdlib && *import_type != ImportType::Stdlib {
return false;
}
true
};
if reverse {
let dependents = deps_index.get_dependents(file_id)?;
let paths = deps_index.get_file_paths(&dependents)?;
match format.as_str() {
"json" => {
let output: Vec<_> = dependents.iter()
.filter_map(|id| paths.get(id).map(|path| serde_json::json!({
"file_id": id,
"path": path,
})))
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} files that import {}", dependents.len(), file_str);
}
"tree" => {
println!("Files that import {}:", file_str);
for (id, path) in &paths {
if dependents.contains(id) {
println!(" └─ {}", path);
}
}
eprintln!("\nFound {} dependents", dependents.len());
}
"table" => {
println!("ID Path");
println!("----- ----");
for id in &dependents {
if let Some(path) = paths.get(id) {
println!("{:<5} {}", id, path);
}
}
eprintln!("\nFound {} dependents", dependents.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table, dot", format);
}
}
} else {
if depth == 1 {
let deps = deps_index.get_dependencies(file_id)?;
let filtered_deps: Vec<_> = deps.into_iter()
.filter(|d| import_filter(&d.import_type))
.collect();
match format.as_str() {
"json" => {
let output: Vec<_> = filtered_deps.iter()
.map(|dep| serde_json::json!({
"imported_path": dep.imported_path,
"resolved_file_id": dep.resolved_file_id,
"import_type": match dep.import_type {
ImportType::Internal => "internal",
ImportType::External => "external",
ImportType::Stdlib => "stdlib",
},
"line": dep.line_number,
"symbols": dep.imported_symbols,
}))
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} dependencies for {}", filtered_deps.len(), file_str);
}
"tree" => {
println!("Dependencies of {}:", file_str);
for dep in &filtered_deps {
let type_label = match dep.import_type {
ImportType::Internal => "[internal]",
ImportType::External => "[external]",
ImportType::Stdlib => "[stdlib]",
};
println!(" └─ {} {} (line {})", dep.imported_path, type_label, dep.line_number);
}
eprintln!("\nFound {} dependencies", filtered_deps.len());
}
"table" => {
println!("Path Type Line");
println!("---------------------------- --------- ----");
for dep in &filtered_deps {
let type_str = match dep.import_type {
ImportType::Internal => "internal",
ImportType::External => "external",
ImportType::Stdlib => "stdlib",
};
println!("{:<28} {:<9} {}", dep.imported_path, type_str, dep.line_number);
}
eprintln!("\nFound {} dependencies", filtered_deps.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table, dot", format);
}
}
} else {
let transitive = deps_index.get_transitive_deps(file_id, depth)?;
let file_ids: Vec<_> = transitive.keys().copied().collect();
let paths = deps_index.get_file_paths(&file_ids)?;
match format.as_str() {
"json" => {
let output: Vec<_> = transitive.iter()
.filter_map(|(id, d)| {
paths.get(id).map(|path| serde_json::json!({
"file_id": id,
"path": path,
"depth": d,
}))
})
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} transitive dependencies (depth {})", transitive.len(), depth);
}
"tree" => {
println!("Transitive dependencies of {} (depth {}):", file_str, depth);
let mut by_depth: std::collections::HashMap<usize, Vec<i64>> = std::collections::HashMap::new();
for (id, d) in &transitive {
by_depth.entry(*d).or_insert_with(Vec::new).push(*id);
}
for depth_level in 0..=depth {
if let Some(ids) = by_depth.get(&depth_level) {
let indent = " ".repeat(depth_level);
for id in ids {
if let Some(path) = paths.get(id) {
if depth_level == 0 {
println!("{}{} (self)", indent, path);
} else {
println!("{}└─ {}", indent, path);
}
}
}
}
}
eprintln!("\nFound {} transitive dependencies", transitive.len());
}
"table" => {
println!("Depth File ID Path");
println!("----- ------- ----");
let mut sorted: Vec<_> = transitive.iter().collect();
sorted.sort_by_key(|(_, d)| *d);
for (id, d) in sorted {
if let Some(path) = paths.get(id) {
println!("{:<5} {:<7} {}", d, id, path);
}
}
eprintln!("\nFound {} transitive dependencies", transitive.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table, dot", format);
}
}
}
}
Ok(())
}
fn handle_deps_circular(
deps_index: &DependencyIndex,
format: &str,
pretty_json: bool,
) -> Result<()> {
let cycles = deps_index.detect_circular_dependencies()?;
if cycles.is_empty() {
println!("No circular dependencies found.");
return Ok(());
}
match format {
"json" => {
let file_ids: Vec<i64> = cycles.iter().flat_map(|c| c.iter()).copied().collect();
let paths = deps_index.get_file_paths(&file_ids)?;
let output: Vec<_> = cycles.iter()
.map(|cycle| {
let cycle_paths: Vec<_> = cycle.iter()
.filter_map(|id| paths.get(id).cloned())
.collect();
serde_json::json!({
"file_ids": cycle,
"paths": cycle_paths,
})
})
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} circular dependencies", cycles.len());
}
"tree" => {
println!("Circular Dependencies Found:");
let file_ids: Vec<i64> = cycles.iter().flat_map(|c| c.iter()).copied().collect();
let paths = deps_index.get_file_paths(&file_ids)?;
for (idx, cycle) in cycles.iter().enumerate() {
println!("\nCycle {}:", idx + 1);
for id in cycle {
if let Some(path) = paths.get(id) {
println!(" → {}", path);
}
}
if let Some(first_id) = cycle.first() {
if let Some(path) = paths.get(first_id) {
println!(" → {} (cycle completes)", path);
}
}
}
eprintln!("\nFound {} cycles", cycles.len());
}
"table" => {
println!("Cycle Files in Cycle");
println!("----- --------------");
let file_ids: Vec<i64> = cycles.iter().flat_map(|c| c.iter()).copied().collect();
let paths = deps_index.get_file_paths(&file_ids)?;
for (idx, cycle) in cycles.iter().enumerate() {
let cycle_str = cycle.iter()
.filter_map(|id| paths.get(id).map(|p| p.as_str()))
.collect::<Vec<_>>()
.join(" → ");
println!("{:<5} {}", idx + 1, cycle_str);
}
eprintln!("\nFound {} cycles", cycles.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table", format);
}
}
Ok(())
}
/// Handle --hotspots flag (most-imported files)
fn handle_deps_hotspots(
deps_index: &DependencyIndex,
format: &str,
pretty_json: bool,
limit: Option<usize>,
) -> Result<()> {
let hotspots = deps_index.find_hotspots(limit)?;
if hotspots.is_empty() {
println!("No hotspots found.");
return Ok(());
}
let file_ids: Vec<i64> = hotspots.iter().map(|(id, _)| *id).collect();
let paths = deps_index.get_file_paths(&file_ids)?;
match format {
"json" => {
let output: Vec<_> = hotspots.iter()
.filter_map(|(id, count)| {
paths.get(id).map(|path| serde_json::json!({
"file_id": id,
"path": path,
"import_count": count,
}))
})
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} hotspots", hotspots.len());
}
"tree" => {
println!("Hotspots (Most-Imported Files):");
for (idx, (id, count)) in hotspots.iter().enumerate() {
if let Some(path) = paths.get(id) {
println!(" {}. {} ({} imports)", idx + 1, path, count);
}
}
eprintln!("\nFound {} hotspots", hotspots.len());
}
"table" => {
println!("Rank Imports File");
println!("---- ------- ----");
for (idx, (id, count)) in hotspots.iter().enumerate() {
if let Some(path) = paths.get(id) {
println!("{:<4} {:<7} {}", idx + 1, count, path);
}
}
eprintln!("\nFound {} hotspots", hotspots.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table", format);
}
}
Ok(())
}
/// Handle --unused flag (orphaned files)
fn handle_deps_unused(
deps_index: &DependencyIndex,
format: &str,
pretty_json: bool,
limit: Option<usize>,
) -> Result<()> {
let mut unused = deps_index.find_unused_files()?;
if let Some(lim) = limit {
unused.truncate(lim);
}
if unused.is_empty() {
println!("No unused files found (all files have incoming dependencies).");
return Ok(());
}
let paths = deps_index.get_file_paths(&unused)?;
match format {
"json" => {
let output: Vec<_> = unused.iter()
.filter_map(|id| {
paths.get(id).map(|path| serde_json::json!({
"file_id": id,
"path": path,
}))
})
.collect();
let json_str = if pretty_json {
serde_json::to_string_pretty(&output)?
} else {
serde_json::to_string(&output)?
};
println!("{}", json_str);
eprintln!("Found {} unused files", unused.len());
}
"tree" => {
println!("Unused Files (No Incoming Dependencies):");
for (idx, id) in unused.iter().enumerate() {
if let Some(path) = paths.get(id) {
println!(" {}. {}", idx + 1, path);
}
}
eprintln!("\nFound {} unused files", unused.len());
}
"table" => {
println!("File ID Path");
println!("------- ----");
for id in &unused {
if let Some(path) = paths.get(id) {
println!("{:<7} {}", id, path);
}
}
eprintln!("\nFound {} unused files", unused.len());
}
_ => {
anyhow::bail!("Unknown format '{}'. Supported: json, tree, table", format);
}
}
Ok(())
}
/// Handle --islands flag (disconnected components)
fn handle_deps_islands(
_deps_index: &DependencyIndex,
_format: &str,
_pretty_json: bool,
) -> Result<()> {
anyhow::bail!("--islands is not yet implemented. Coming soon!")
}
/// Handle --analyze flag (full report)
fn handle_deps_analyze(
deps_index: &DependencyIndex,
format: &str,
pretty_json: bool,
limit: Option<usize>,
) -> Result<()> {
println!("Running comprehensive dependency analysis...\n");
println!("1. Circular Dependencies:");
handle_deps_circular(deps_index, format, pretty_json)?;
println!("\n2. Hotspots (Most-Imported Files):");
handle_deps_hotspots(deps_index, format, pretty_json, limit)?;
println!("\n3. Unused Files:");
handle_deps_unused(deps_index, format, pretty_json, limit)?;
println!("\nAnalysis complete!");
Ok(())
}
```
---
### Testing Plan
#### Manual Testing Steps
1. **Basic operations:**
```bash
rfx deps src/main.rs
rfx deps src/main.rs --reverse
rfx deps src/main.rs --depth 2
rfx deps src/main.rs --format tree
rfx deps src/main.rs --format table
```
2. **Graph-wide operations:**
```bash
rfx deps --circular
rfx deps --hotspots --limit 10
rfx deps --unused
rfx deps --analyze
```
3. **Filters:**
```bash
rfx deps src/main.rs --only-internal
rfx deps src/main.rs --only-external
rfx deps src/main.rs --reverse --format tree
```
4. **Edge cases:**
```bash
rfx deps nonexistent.rs # Should error gracefully
rfx deps src/main.rs --depth 0 # Should show self only
rfx deps --circular --format json --pretty
```
#### Integration Test Outline (`tests/deps_test.rs`)
```rust
#[test]
fn test_deps_direct() {
}
#[test]
fn test_deps_reverse() {
}
#[test]
fn test_deps_transitive() {
}
#[test]
fn test_deps_circular() {
}
#[test]
fn test_deps_hotspots() {
}
#[test]
fn test_deps_unused() {
}
```
---
### Success Criteria
**The implementation is complete when:**
1. ✅ `rfx deps <file>` shows direct dependencies in JSON format
2. ✅ `rfx deps <file> --reverse` shows dependents
3. ✅ `rfx deps <file> --depth N` traverses to depth N
4. ✅ `rfx deps <file> --format tree` displays tree visualization
5. ✅ `rfx deps <file> --format table` displays tabular output
6. ✅ `rfx deps --circular` detects and displays cycles
7. ✅ `rfx deps --hotspots` ranks most-imported files
8. ✅ `rfx deps --unused` finds orphaned files
9. ✅ `rfx deps --analyze` runs comprehensive report
10. ✅ All filters work: `--only-internal`, `--only-external`, `--only-stdlib`
11. ✅ Error handling is graceful (file not found, no index, etc.)
12. ✅ Integration tests pass
13. ✅ Documentation updated in CLAUDE.md
---
### Estimated Time to Completion
**Phase 1:** 2-3 hours (basic command + JSON output)
**Phase 2:** 3-4 hours (formatters: tree, table, dot)
**Phase 3:** 2-3 hours (graph-wide operations)
**Phase 4:** 2-3 hours (testing + docs)
**Total:** 9-13 hours (~1-2 days of focused work)
---
### Open Questions
1. **DOT format support:** Should we implement Graphviz DOT format in Phase 2, or defer to Phase 6?
- **Recommendation:** Defer to Phase 6 (low priority, requires additional dependencies)
2. **--islands implementation:** Connected components analysis requires Union-Find or multi-source BFS
- **Recommendation:** Implement in Phase 3 or defer to Phase 6
3. **Caching strategy:** Should we cache graph traversal results for repeated queries?
- **Recommendation:** Not needed for MVP - queries are fast enough (<50ms)
4. **MCP integration:** Should `rfx deps` be exposed via MCP server?
- **Recommendation:** Yes, add in Phase 3 after core implementation is stable
---
### Next Steps
**To start implementation:**
1. Create a new git branch: `git checkout -b feature/rfx-deps-command`
2. Begin with Phase 1: Add `Deps` command to CLI
3. Test incrementally after each phase
4. Document as you go (update CLAUDE.md examples)
5. Open PR when Phases 1-3 are complete and tested