Module: lash-core (dependency resolver)
Dependencies: tasks.core-data-model.md, tasks.sqlite-schema.md, tasks.markdown-parser.md
Effort: 10-14 days
Priority: CRITICAL
The dependency resolution system builds and analyzes the task dependency graph. It handles three types of dependencies:
- Implicit hierarchy - parent tasks depend on children
- Explicit cross-file references -
@depends-onannotations - Directory-level dependencies - directory structure relationships
It must detect cycles, compute completion status, identify blockers, and support efficient queries.
From design-doc.md:
- Model dependencies as a directed graph (section 5)
- Detect circular dependencies (section 5)
- Compute task completion status based on dependencies (section 5.4)
- Support within-file and cross-file dependencies (sections 5.1, 5.3)
- Handle waived tasks (section 5.1)
- Identify blocked tasks (section 5.4)
Priority: CRITICAL Effort: 2-3 days Depends on: tasks.core-data-model.md#1
Define the in-memory graph representation for the task dependency network.
- Define
DependencyGraphstruct- Node storage (task IDs -> Task references)
- Edge storage (adjacency list: task_id -> Vec<task_id>)
- Reverse edge storage (for efficient reverse lookups)
- Edge metadata (dependency type: hierarchy, explicit, directory)
- Implement graph construction from DB
- Load all tasks and dependencies from SQLite
- Build adjacency lists
- Index tasks by full_id for fast lookup
- Implement graph query methods
-
get_dependencies(task_id)- direct dependencies -
get_dependents(task_id)- reverse dependencies -
get_descendants(task_id)- transitive closure (DFS/BFS) -
get_ancestors(task_id)- reverse transitive closure
-
- Add edge type tracking
- Distinguish hierarchy vs explicit vs directory edges
- Store source location for explicit edges (for error reporting)
- Graph correctly represents all dependency relationships
- Efficient lookups: O(1) for direct dependencies, O(E+V) for transitive
- Memory-efficient for large graphs (1000+ tasks)
- Clear API for downstream consumers
- Unit: Build graph from fixture data
- Unit: Test query methods on various graph structures
- Unit: Test edge type tracking
- Performance: Measure memory usage for large graphs
Priority: CRITICAL Effort: 2-3 days Depends on: Task 1
Detect circular dependencies in the task graph using standard graph algorithms.
- Implement
CycleDetectorstruct- Use DFS with color marking (white/gray/black)
- Track path during traversal for cycle reporting
- Implement
detect_cycles()function- Run DFS from all unvisited nodes
- Detect back edges (gray -> gray)
- Collect all cycles (not just first)
- Return cycle paths with task IDs
- Add cycle reporting
- Format cycle as: Task A -> Task B -> Task C -> Task A
- Include file paths and line numbers
- Distinguish cycle types (within-file vs cross-file)
- Implement cycle resolution suggestions
- Identify weakest link (e.g., directory dep vs explicit)
- Suggest breaking explicit dependencies
- Suggest restructuring hierarchy
- Detects all cycles in arbitrary graphs
- Correctly handles graphs with multiple disjoint cycles
- Clear, actionable error messages for each cycle
- No false positives or false negatives
- Unit: Acyclic graph (no cycles)
- Unit: Simple cycle (A -> B -> A)
- Unit: Complex cycle (A -> B -> C -> D -> B)
- Unit: Multiple disjoint cycles
- Unit: Self-loop (A -> A)
- Integration: Test with fixture files containing cycles
Priority: CRITICAL Effort: 3-4 days Depends on: Task 1, tasks.markdown-parser.md#2
Parse dependency references from tasks and build the complete dependency graph.
- Implement
DependencyResolverstruct- Hold references to parsed tasks and files
- Track resolution errors (broken links)
- Implement implicit hierarchy resolution
- For each task, add edges to all children
- Edges are
(parent_id, child_id, type=hierarchy) - Note: Implemented in indexing layer (
DependencyUpdater.insert_hierarchy_dependencies())
- Implement explicit
@depends-onresolution- Parse
@depends-onannotations - Support formats:
- Relative path:
../core/cli.md#task:parse-args - Absolute path:
core/cli.md#task:parse-args - ID-only:
#task:parse-args(within-file) - File-level:
../core/cli.md(depends on all top-level tasks in file) - Bare file ID:
file-id(file-level dependency via ID)
- Relative path:
- Resolve paths relative to source file
- Look up target task in DB/graph
- Create edge
(source_id, target_id, type=explicit) - Handle missing targets (broken links)
- Parse
- Implement directory-level dependencies
- Parse directory references (format:
path/to/dir/) - Find all files in the target directory (immediate children only)
- Create dependencies to top-level tasks in each file
- Report error if directory contains no task files
- Parse directory references (format:
- Handle broken dependencies
- Collect all broken references
- Return structured errors with source location
- Mark affected tasks as potentially blocked - implemented in status computation
- Correctly resolves all dependency types (explicit ID, explicit path, within-file, file-level, directory)
- Handles all supported reference formats
- Detects and reports broken links with precise locations
- Produces complete, accurate dependency graph
- Unit: Resolve implicit hierarchy - tests in indexing layer (
test_hierarchy_dependencies_created) - Unit: Resolve explicit cross-file dependencies
- Unit: Resolve various path formats
- Unit: Handle broken links gracefully
- Unit: File-level dependencies (path and bare ID formats)
- Unit: Directory dependencies (basic, top-level only, not recursive, not found)
- [-] Integration: Build graph from fixture project - deferred
- [-] Integration: Verify graph structure matches expectations - deferred
Completed:
- Created
crates/lash-core/src/dependency/resolver.rswithDependencyResolver - Supports path-based and ID-based dependency references
- Handles relative path resolution with
normalize_path()helper - Collects all resolution errors without failing fast
- Provides detailed error messages with source locations
- All unit tests passing (15 tests)
- All doctests passing (3 tests)
File-Level Dependencies (December 2024):
- Implemented file-level dependencies:
../path/file.md(without#taskfragment) - Implemented bare file ID dependencies:
file-id(without#task-id) - Both formats create dependencies to all top-level tasks (depth == 0) in the target file
- Nested tasks are excluded to avoid bloating the dependency graph
Directory-Level Dependencies (December 2024):
- Implemented directory reference resolution:
path/to/dir/(must end with/) - Creates dependencies to top-level tasks in all files directly in the directory
- Does NOT recurse into subdirectories (immediate children only)
- Reports
DirectoryNotFounderror if directory contains no task files - Added
ResolutionErrorKind::DirectoryNotFoundvariant for error handling - Changed
resolve_reference()to returnVec<ResolvedDependency>to support multiple targets
Test Coverage (7 new tests):
test_file_level_dependency_path_reference- verifies top-level task filteringtest_file_level_dependency_bare_file_id- bare file ID formattest_directory_dependency_basic- multiple files in directorytest_directory_dependency_top_level_only- excludes nested taskstest_directory_dependency_not_recursive- excludes subdirectoriestest_directory_dependency_not_found- error handlingtest_directory_dependency_kind- verifiesDependencyKind::Directory
Changes to lash-types:
- Fixed
parse_dependency_ref()to correctly detect path references with#fragments - Fixed
DependencyRef::validate()to check only the path part (before#) - Added
TaskTree::get_task_mut()for modifying tasks in tests
Priority: HIGH Effort: 2-3 days Depends on: Task 1, Task 3
Compute the effective completion status of each task based on its own status and its dependencies.
- Implement
StatusComputerstruct- Take graph and task statuses as input
- Compute effective status for each task
- Implement completion rules (from design doc section 5.4)
- Task is complete if:
- Own status is
done, AND - All children are
doneorwaived, AND - All explicit dependencies are complete or waived
- Own status is
- Task is blocked if:
- Any dependency is
openorblocked(not waived), OR - Depends on broken link
- Any dependency is
- Task is complete if:
- Implement
compute_status()function- Topological traversal (or recursive with memoization)
- Cache computed statuses to avoid recomputation
- Handle waived dependencies (ignore in completion check)
- Add file-level completion status
- File complete if all top-level tasks complete
- [-] Consider directory-level dependencies (deferred)
- Detect inconsistencies
- Parent marked done but children open (lint warning)
- Task marked done but dependencies open
- Correctly computes status for all tasks in graph
- Respects waived tasks (treats as complete)
- Identifies blocked tasks accurately
- Efficient: O(V+E) traversal, with memoization
- Unit: Simple chain (A -> B -> C), various states
- Unit: Waived dependencies ignored
- Unit: Blocked propagation (A blocks B, B blocks C)
- Unit: Parent/child status consistency
- [-] Integration: Compute status for entire fixture project (deferred)
Completed:
- Created
crates/lash-core/src/dependency/status_computer.rswith complete implementation - Implemented
ComputedStatusenum with Complete, Incomplete, Blocked, and Inconsistent variants - Implemented
BlockerReasonenum to provide detailed information about why tasks are blocked - Implemented
InconsistencyKindenum to identify different types of status inconsistencies - Used recursive DFS with memoization for efficient O(V+E) status computation
- Handles cycle detection during status computation
- Distinguishes between hierarchy and explicit dependencies for inconsistency detection
- File-level completion status computed based on top-level tasks (depth 0)
- All unit tests passing (14 tests)
- All doctests passing (4 tests)
- Exported from
crates/lash-core/src/dependency/mod.rs
Algorithm:
- Recursive status computation with memoization cache
- Uses visiting set for cycle detection
- Waived tasks always treated as complete
- Blocked status propagates through dependency chains
- Inconsistencies detected when done tasks have incomplete dependencies
- Separates parent/child inconsistencies from explicit dependency inconsistencies
Test Coverage:
- Single task states (done, open, waived)
- Simple dependency chains
- Waived dependency handling
- Blocked dependency propagation
- Multiple blockers
- Parent-child inconsistencies
- Done tasks with incomplete explicit dependencies
- File-level status computation
- Cycle detection
Deferred:
- Directory-level dependencies (not yet implemented in graph)
- Integration tests with full fixture projects (to be added later)
Priority: HIGH Effort: 2 days Depends on: Task 4
Given a task, identify which dependencies are blocking its completion.
- Implement
BlockerAnalyzerstruct- Query graph and status for dependencies
- Identify incomplete dependencies
- Implement
find_blockers(task_id)function- Get all dependencies (direct + transitive)
- Filter to incomplete/blocked tasks
- Sort by "distance" (direct blockers first)
- Return list of blocker tasks with reasons
- Add blocker chain analysis
- For each blocker, recursively find its blockers
- Build blocker tree/graph
- Identify "root blockers" (no further dependencies)
- Implement blocker reporting
- Format: "Task X is blocked by: Task Y (in file Z)"
- Show full blocker chain for deep dependencies
- Suggest actions (complete blockers, waive, remove dependency)
- Accurately identifies all blockers for a given task
- Handles transitive blockers (A blocked by B blocked by C)
- Clear, actionable blocker reports
- Efficient: O(E) for direct blockers, O(V+E) for transitive
- Unit: Task with direct blocker
- Unit: Task with transitive blocker chain
- Unit: Task with multiple independent blockers
- Unit: Task with no blockers (ready to start)
- Integration: Generate blocker report for fixture tasks
Completed:
- Created
crates/lash-core/src/dependency/blocker_analyzer.rswith complete implementation - Implemented
BlockerAnalyzerwith comprehensive blocker identification - Implemented
BlockerInfostruct with depth tracking and blocker metadata - Implemented
BlockerChainfor showing transitive blocker relationships - Implemented
BlockerReportwith human-readable formatting - Implemented
BlockerSuggestionenum for actionable resolution suggestions - Uses BFS to find all blockers with depth tracking (0=direct, 1+=transitive)
- Root blocker identification (tasks with no incomplete dependencies)
- Deduplication to avoid repeated blockers via multiple paths
- All unit tests passing (7 tests)
- All doctests passing (7 tests)
- Exported from
crates/lash-core/src/dependency/mod.rs
Algorithm:
- BFS traversal starting from direct dependencies
- Depth tracking to distinguish direct vs transitive blockers
- Only follows paths through blocked or incomplete tasks
- Sorts results by depth (direct blockers first)
- Root blockers identified as incomplete tasks with no blockers themselves
Data Structures:
BlockerInfo: Contains task_id, title, file_id, depth, dependency_kind, and blocker_statusBlockerChain: Shows recursive blocker relationships from direct to rootBlockerReport: Formatted output with blockers, chains, roots, and suggestionsBlockerSuggestion: Actionable recommendations (complete, waive, remove dependency)
Report Format:
- Summary: Total blockers, direct vs transitive counts
- Root blockers section (most important - address first)
- Blocker chains showing dependency paths (A β B β C)
- All blockers listed with depth and status
- Suggested actions prioritizing root blockers
Test Coverage:
- Direct blocker identification
- Transitive blocker chains
- Multiple independent blockers
- No blockers (ready to start)
- Completed dependencies not treated as blockers
- Blocker chain construction
- Report generation and formatting
Integration:
- Uses
DependencyGraphfor traversal - Uses
StatusComputer::compute_all()for task statuses - Builds on existing
ComputedStatusfrom status_computer - Provides detailed analysis beyond basic status computation
Priority: MEDIUM Effort: 1-2 days Depends on: Task 1
Export dependency graph in various formats for visualization and analysis.
- Implement
GraphExporterstruct- Support multiple output formats
- Implement DOT format export
- Generate Graphviz-compatible DOT file
- Nodes: task IDs or titles
- Edges: dependency relationships
- Color-code by status (green=done, yellow=open, coral=blocked, gray=waived)
- Cluster by file or directory
- Implement JSON export
- Nodes array: task metadata
- Edges array: source/target/type
- Include status (labels not yet in NodeData)
- Add filtering options
- Export subgraph (specific file or label)
- Hide completed tasks
- Show only direct dependencies (max_depth option)
- Implement text-based graph visualization
- ASCII tree format for terminal display
- Indent by depth
- Show dependency arrows with status indicators
- DOT output renders correctly in Graphviz (valid syntax verified)
- JSON format is parsable and complete
- Filtering options work as expected
- Text format is readable in terminal
- Unit: Export empty graph (DOT and JSON)
- Unit: Export simple graph to DOT
- Unit: Export simple graph to JSON
- Unit: Export ASCII tree (simple and nested)
- Unit: Filter by file
- Unit: Filter by completion status
- Unit: Filter by max depth
- Unit: Multiple file clustering
- Unit: Cycle detection in ASCII tree
- Unit: DOT special character escaping
- [-] Integration: Export fixture project graph, verify correctness (deferred)
- [-] Manual: Render DOT file with Graphviz, inspect visually (deferred)
Completed:
- Created
crates/lash-core/src/dependency/graph_exporter.rswith complete implementation - Implemented
GraphExporterstruct with three export formats - Implemented
FilterOptionsfor flexible subgraph export - DOT format with Graphviz syntax:
- Color-coded nodes (lightgreen=done, lightyellow=open, lightcoral=blocked, lightgray=waived)
- File-based clustering with subgraphs
- Labeled edges with dependency kind
- Proper escaping of special characters
- JSON format with serde serialization:
- Separate nodes and edges arrays
- Full task metadata (id, title, status, file_id, depth)
- Edge metadata (from, to, kind, source_location)
- ASCII tree format for terminal display:
- Recursive tree rendering with proper indentation
- Status indicators: [ ] open, [β] done, [-] waived, [!] blocked
- Cycle detection to prevent infinite recursion
- Box-drawing characters (ββ, ββ, β) for visual structure
- Filter options:
- Filter by file IDs
- Hide completed tasks (done/waived)
- Max depth for limiting transitive dependencies
- Label filtering placeholder (labels not yet in NodeData)
- All unit tests passing (12 tests)
- All doctests passing (7 tests)
- Exported from
crates/lash-core/src/dependency/mod.rs
Data Structures:
GraphExporter<'a>: Borrows graph reference for exportFilterOptions: Configure which nodes/edges to includeJsonGraph,JsonNode,JsonEdge: Serde-compatible JSON representation
Export Formats:
- DOT: Graphviz-compatible directed graph with clustering
- JSON: Structured data for programmatic consumption
- ASCII tree: Terminal-friendly visualization starting from a root node
Test Coverage:
- Empty graph export (both formats)
- Simple graphs with nodes and edges
- ASCII tree rendering (simple and nested)
- Filter by file
- Filter by completion status
- Filter by depth
- Multiple file clustering
- Cycle detection
- Special character escaping
Integration:
- Uses
DependencyGraphfor graph queries - Respects
TaskStatusenum including Blocked state - Compatible with existing dependency module APIs
Priority: MEDIUM Effort: 2-3 days Depends on: Task 3, tasks.indexing.md#5
Support efficient graph updates when tasks or dependencies change, without full rebuild.
Phase 1: Core Graph Mutation Operations (COMPLETE)
- Define
GraphErrorenum for error handling - Define
GraphResult<T>type alias - Implement
remove_nodewith force option- Error if node has dependents (unless force=true)
- Remove all associated edges (incoming and outgoing)
- Maintain bidirectional edge consistency
- Implement
update_nodeto replace node metadata - Implement
update_node_statusfor status-only updates (optimized) - Implement
remove_edgeto remove dependency relationships- Update forward adjacency list
- Update reverse adjacency list
- Remove edge metadata
- Implement
update_edgeto replace edge metadata - Add comprehensive unit tests (13 tests)
- Add doctests for all public mutation methods
- Export GraphError and GraphResult from mod.rs
Phase 2: Batch Update Operations (COMPLETE)
- Implement
add_nodesfor bulk node insertion- Pre-allocate space to minimize reallocations
- Implement
remove_nodesfor bulk node removal- Fail-fast on first error (or force remove all)
- Implement
add_edgesfor bulk edge insertion- Pre-allocate space for edge metadata
- Implement
remove_edgesfor bulk edge removal- Fail-fast on first error
- Add comprehensive unit tests (6 tests)
- Add doctests for all batch operations
Phase 3: Incremental Dependency Re-resolution (COMPLETE)
- Create
GraphChangesstruct to track modifications- Track added/removed/modified nodes
- Track status-only changes separately
- Track added/removed/modified edges
- Implement change classification methods
-
has_structural_changes()- detect graph structure changes -
is_status_only()- detect pure status updates -
is_empty()- check if any changes
-
- Implement
compute_affected_nodesto determine recomputation scope- Include all modified nodes
- Propagate to all ancestors (transitive dependents)
- Handle edge changes correctly
- Implement utility methods
-
merge()- combine multiple change sets -
clear()- reset change tracker
-
- Add comprehensive unit tests (11 tests)
- Add doctests with realistic examples
Phase 4: Optimization for Common Cases (IMPLEMENTED)
- Fast path for status-only changes (via
update_node_status)- O(1) status update without copying node data
- Change detection via
GraphChanges-
has_structural_changes()determines if cycle detection needed -
is_status_only()enables optimized status recomputation
-
- [-] Benchmarks comparing incremental vs full rebuild (deferred)
- Note: Benchmarks deferred to future performance optimization phase
- All common operations optimized for minimal allocations
Phase 5: Integration and Documentation (COMPLETE)
- Comprehensive doctests for all public APIs
- All mutation methods (5 doctests)
- All batch operations (4 doctests)
- GraphChanges with realistic usage (2 doctests)
- Unit test coverage
- Phase 1: 13 tests (mutation operations)
- Phase 2: 6 tests (batch operations)
- Phase 3: 11 tests (change tracking)
- Total: 30 new tests, all passing
- Clear error messages via GraphError enum
- Performance characteristics documented in doc comments
- [-] Integration tests with full workflow (deferred to future)
- Note: Graph mutation layer complete; integration with resolver deferred
- Core mutation operations maintain graph invariants
- Comprehensive test coverage (30 unit tests, 11 doctests)
- Clear error handling with helpful messages (GraphError enum)
- Graph remains consistent after all operations
- Change tracking enables incremental updates
- Optimized for common cases (status-only updates)
- Unit: Remove node (simple, with dependents, force removal) - 4 tests
- Unit: Update node metadata and status - 4 tests
- Unit: Remove/update edges - 5 tests
- Unit: Graph invariants maintained after mutations - all tests verify
- Unit: Batch operations - 6 tests
- Unit: Change tracking (GraphChanges) - 11 tests
- [-] Integration: Incremental update after file modification (deferred)
- [-] Benchmarks: Performance comparison (deferred)
All Phases Complete (Task 7):
Phase 1 - Core Mutations:
- Added
GraphErrorenum with three variants:NodeNotFound: Node doesn't existEdgeNotFound: Edge doesn't existNodeHasDependents: Cannot remove node with dependents
- Implemented 5 mutation methods on
DependencyGraph:remove_node(task_id, force): Remove node and edgesupdate_node(task_id, node_data): Replace node metadataupdate_node_status(task_id, status): Optimized status update (O(1))remove_edge(from_id, to_id): Remove dependency relationshipupdate_edge(from_id, to_id, edge_data): Replace edge metadata
- Added internal helper
remove_edge_internalfor efficient bulk removal - All mutation methods maintain bidirectional edge consistency
- 13 unit tests + 5 doctests
Phase 2 - Batch Operations:
- Implemented 4 batch methods:
add_nodes(nodes): Bulk node insertion with pre-allocationremove_nodes(task_ids, force): Bulk node removaladd_edges(edges): Bulk edge insertion with pre-allocationremove_edges(edges): Bulk edge removal
- All batch operations optimize for minimal reallocations
- Fail-fast error handling (returns first error encountered)
- 6 unit tests + 4 doctests
Phase 3 - Change Tracking:
- Implemented
GraphChangesstruct with comprehensive change tracking:- Tracks added/removed/modified nodes
- Tracks status-only changes separately
- Tracks added/removed/modified edges
- Change classification methods:
has_structural_changes(): Detect graph structure changesis_status_only(): Detect pure status updatesis_empty(): Check if any changes occurred
- Implemented
compute_affected_nodes(graph):- Computes transitive closure of affected nodes
- Propagates changes to all ancestors
- Enables incremental status recomputation
- Utility methods:
merge(),clear() - 11 unit tests + 2 doctests
Phase 4 - Optimizations:
- Status-only updates use O(1) in-place mutation
- Batch operations pre-allocate to minimize reallocations
- Change detection enables smart recomputation
- Structural changes β full cycle detection needed
- Status-only changes β incremental status update sufficient
Test Summary:
- Total: 30 new unit tests, 11 new doctests
- All tests passing (66 unit tests, 34 doctests overall)
- Full coverage of mutation operations, batch operations, and change tracking
- All graph invariants maintained across all operations
Exports:
- Added to
dependency/mod.rs:GraphError,GraphResultGraphChanges
- All public APIs have executable doctests
- Advanced graph algorithms (shortest path, etc.)
- Graph persistence to disk (rebuild from DB each time)
- Multi-graph support (separate graphs for different views)
- Real-time graph updates (manual recomputation)
- Cycle handling: Fail-fast vs collect all cycles?
- Waived propagation: If A waives B, does B's status matter?
- Performance: Build graph on-demand vs cache in memory?
- Directory dependencies: Explicit annotation vs inferred from structure?
- Design doc section 5 (Dependency model)
- Design doc section 5.4 (Completion semantics)
- Design doc section 7.3.4 (Graph commands)
/docs/dependency-graph-architecture.md- Graph architecture design