MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Analysis/API_REFERENCE.md

932 lines
25 KiB
Markdown
Executable File

# API Reference
## MarketAlly.AIPlugin.Analysis
**Version:** 2.1.0
**Target Framework:** .NET 8.0
**Generated:** 2025-06-24
---
## Table of Contents
- [Infrastructure Classes](#infrastructure-classes)
- [AnalysisConfiguration](#analysisconfiguration)
- [AnalysisContext](#analysiscontext)
- [ErrorHandling](#errorhandling)
- [PerformanceOptimization](#performanceoptimization)
- [PluginDiscoveryService](#plugindiscoveryservice)
- [AnalysisResultAggregator](#analysisresultaggregator)
- [InputValidator](#inputvalidator)
- [Analysis Plugins](#analysis-plugins)
- [Data Models](#data-models)
- [Interfaces](#interfaces)
- [Examples](#examples)
---
## Infrastructure Classes
### AnalysisConfiguration
Configuration management for analysis operations.
```csharp
public class AnalysisConfiguration
```
#### Properties
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| `DefaultParameters` | `Dictionary<string, object>` | Default parameters for plugin execution | `new()` |
| `DefaultTimeout` | `TimeSpan` | Default timeout for operations | `10 minutes` |
| `MaxConcurrentAnalyses` | `int` | Maximum concurrent analysis operations | `Environment.ProcessorCount` |
| `EnableCaching` | `bool` | Enable result caching | `true` |
| `CacheExpirationTime` | `TimeSpan` | Cache expiration time | `30 minutes` |
| `AllowDynamicPluginLoading` | `bool` | Allow loading external plugins | `false` |
| `TrustedPluginDirectory` | `string` | Directory for trusted plugins | `""` |
| `MaxMemoryUsage` | `long` | Maximum memory usage in bytes | `1GB` |
| `EnableDetailedLogging` | `bool` | Enable detailed logging | `false` |
#### Usage Example
```csharp
var config = new AnalysisConfiguration
{
DefaultTimeout = TimeSpan.FromMinutes(15),
MaxConcurrentAnalyses = 8,
EnableCaching = true,
CacheExpirationTime = TimeSpan.FromHours(2),
DefaultParameters = new Dictionary<string, object>
{
["analyzeComplexity"] = true,
["includeRecommendations"] = true
}
};
```
---
### AnalysisContext
Resource management context for analysis operations implementing `IDisposable`.
```csharp
public class AnalysisContext : IDisposable
```
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| `CancellationToken` | `CancellationToken` | Cancellation token for operations |
| `Configuration` | `AnalysisConfiguration` | Analysis configuration |
| `Logger` | `ILogger?` | Logger instance |
| `ConcurrencySemaphore` | `SemaphoreSlim` | Concurrency control semaphore |
#### Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `CreateChildContext()` | `AnalysisContext` | Creates linked child context |
| `Cancel()` | `void` | Cancels the analysis operation |
| `AcquireConcurrencySlotAsync()` | `Task` | Waits for concurrency slot |
| `ReleaseConcurrencySlot()` | `void` | Releases concurrency slot |
| `Dispose()` | `void` | Disposes resources |
#### Usage Example
```csharp
using var context = new AnalysisContext(configuration, logger);
try
{
await context.AcquireConcurrencySlotAsync();
// Perform analysis
}
finally
{
context.ReleaseConcurrencySlot();
}
```
---
### ErrorHandling
Static utility class for comprehensive error handling with retry logic.
```csharp
public static class ErrorHandling
```
#### Methods
##### ExecuteWithRetryAsync&lt;T&gt;
Executes operation with retry logic and exponential backoff.
```csharp
public static async Task<T> ExecuteWithRetryAsync<T>(
Func<Task<T>> operation,
int maxRetries = 3,
TimeSpan? delay = null,
ILogger? logger = null,
CancellationToken cancellationToken = default,
[CallerMemberName] string callerName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
```
**Parameters:**
- `operation`: The operation to execute
- `maxRetries`: Maximum retry attempts (default: 3)
- `delay`: Base delay between retries (default: 1 second)
- `logger`: Logger for error tracking
- `cancellationToken`: Cancellation token
- `callerName`: Automatic caller name
- `callerFilePath`: Automatic caller file path
- `callerLineNumber`: Automatic caller line number
##### SafeExecuteAsync&lt;T&gt;
Safely executes operation and returns result with error information.
```csharp
public static async Task<OperationResult<T>> SafeExecuteAsync<T>(
Func<Task<T>> operation,
ILogger? logger = null,
[CallerMemberName] string callerName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
```
##### WithTimeoutAsync&lt;T&gt;
Creates timeout wrapper for operations.
```csharp
public static async Task<T> WithTimeoutAsync<T>(
Func<CancellationToken, Task<T>> operation,
TimeSpan timeout,
ILogger? logger = null,
[CallerMemberName] string callerName = "")
```
##### HandlePluginException
Handles exceptions from plugin operations with detailed logging.
```csharp
public static PluginErrorInfo HandlePluginException(
Exception exception,
string pluginName,
string operationName,
ILogger? logger = null)
```
#### Usage Example
```csharp
// Retry with exponential backoff
var result = await ErrorHandling.ExecuteWithRetryAsync(
() => CallExternalServiceAsync(),
maxRetries: 5,
delay: TimeSpan.FromSeconds(2),
logger: logger
);
// Safe execution with error handling
var operationResult = await ErrorHandling.SafeExecuteAsync(
() => RiskyOperationAsync(),
logger: logger
);
if (operationResult.IsSuccess)
{
Console.WriteLine($"Success: {operationResult.Value}");
}
else
{
Console.WriteLine($"Error: {operationResult.ErrorMessage}");
}
```
---
### PerformanceOptimization
Performance optimization utilities including caching and parallel processing.
```csharp
public class PerformanceOptimization : IDisposable
```
#### Methods
##### ExecuteInParallelAsync&lt;TInput, TResult&gt;
Executes operations in parallel with controlled concurrency.
```csharp
public async Task<IEnumerable<TResult>> ExecuteInParallelAsync<TInput, TResult>(
IEnumerable<TInput> inputs,
Func<TInput, Task<TResult>> operation,
int maxConcurrency = 0,
CancellationToken cancellationToken = default)
```
##### GetOrSetCacheAsync&lt;T&gt;
Gets or sets cached value with automatic invalidation.
```csharp
public async Task<T> GetOrSetCacheAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? expiration = null,
CancellationToken cancellationToken = default)
```
##### ExecuteInBatchesAsync&lt;TInput, TResult&gt;
Batches operations for efficient processing.
```csharp
public async Task<IEnumerable<TResult>> ExecuteInBatchesAsync<TInput, TResult>(
IEnumerable<TInput> inputs,
Func<IEnumerable<TInput>, Task<IEnumerable<TResult>>> batchOperation,
int batchSize = 100,
CancellationToken cancellationToken = default)
```
##### CreateObjectPool&lt;T&gt;
Creates object pool for expensive-to-create objects.
```csharp
public ObjectPool<T> CreateObjectPool<T>(
Func<T> factory,
Action<T>? resetAction = null,
int maxSize = 10) where T : class
```
#### Usage Example
```csharp
var perfOptimizer = new PerformanceOptimization();
// Parallel execution
var results = await perfOptimizer.ExecuteInParallelAsync(
files,
async file => await AnalyzeFileAsync(file),
maxConcurrency: Environment.ProcessorCount
);
// Caching
var cachedResult = await perfOptimizer.GetOrSetCacheAsync(
"expensive_calculation",
() => PerformExpensiveCalculationAsync(),
TimeSpan.FromHours(1)
);
// Object pooling
var stringBuilderPool = perfOptimizer.CreateObjectPool(
() => new StringBuilder(),
sb => sb.Clear(),
maxSize: 50
);
```
---
### PluginDiscoveryService
Service for discovering and loading analysis plugins.
```csharp
public class PluginDiscoveryService : IPluginDiscovery
```
#### Methods
##### DiscoverPluginsAsync
Discovers plugins in specified directory.
```csharp
public async Task<IEnumerable<IAIPlugin>> DiscoverPluginsAsync(string pluginDirectory)
```
##### LoadPluginAsync
Loads specific plugin from assembly.
```csharp
public async Task<IAIPlugin> LoadPluginAsync(string assemblyPath, string typeName)
```
##### GetBuiltInPlugins
Gets all built-in analysis plugins.
```csharp
public IEnumerable<IAIPlugin> GetBuiltInPlugins()
```
##### ValidatePlugin
Validates plugin implementation.
```csharp
public bool ValidatePlugin(IAIPlugin plugin)
```
#### Usage Example
```csharp
var pluginDiscovery = new PluginDiscoveryService(logger);
// Get built-in plugins
var builtInPlugins = pluginDiscovery.GetBuiltInPlugins();
// Discover external plugins
var externalPlugins = await pluginDiscovery.DiscoverPluginsAsync("./plugins");
// Load specific plugin
var specificPlugin = await pluginDiscovery.LoadPluginAsync(
"CustomAnalyzer.dll",
"CustomAnalyzer.Plugin"
);
// Validate plugin
bool isValid = pluginDiscovery.ValidatePlugin(specificPlugin);
```
---
### AnalysisResultAggregator
Aggregates and analyzes results from multiple plugins.
```csharp
public class AnalysisResultAggregator : IAnalysisResultAggregator
```
#### Methods
##### AggregateAsync
Aggregates results from multiple plugin executions.
```csharp
public async Task<AggregatedResult> AggregateAsync(IEnumerable<AIPluginResult> results)
```
##### CompareResultsAsync
Compares current results with previous results for trend analysis.
```csharp
public async Task<ComparisonResult> CompareResultsAsync(AggregatedResult current, AggregatedResult previous)
```
##### GenerateSummaryAsync
Generates comprehensive summary report.
```csharp
public async Task<SummaryReport> GenerateSummaryAsync(AggregatedResult aggregatedResult)
```
#### Usage Example
```csharp
var aggregator = new AnalysisResultAggregator(logger);
// Aggregate plugin results
var aggregatedResult = await aggregator.AggregateAsync(pluginResults);
// Generate summary
var summary = await aggregator.GenerateSummaryAsync(aggregatedResult);
// Compare with previous results
var comparison = await aggregator.CompareResultsAsync(currentResult, previousResult);
Console.WriteLine($"Health Score: {aggregatedResult.HealthAssessment.Score:F1}");
Console.WriteLine($"Total Issues: {aggregatedResult.AllIssues.Count}");
Console.WriteLine($"Trend: {comparison.TrendDirection}");
```
---
### InputValidator
Input validation and security service.
```csharp
public class InputValidator
```
#### Methods
##### ValidateFilePath
Validates and sanitizes file path.
```csharp
public ValidationResult ValidateFilePath(string? filePath)
```
##### ValidatePluginParameters
Validates plugin parameters for security issues.
```csharp
public ValidationResult ValidatePluginParameters(Dictionary<string, object>? parameters)
```
##### ValidateConfiguration
Validates analysis configuration settings.
```csharp
public ValidationResult ValidateConfiguration(AnalysisConfiguration? config)
```
##### SanitizeInput
Sanitizes string input to remove dangerous content.
```csharp
public string SanitizeInput(string? input)
```
##### ValidateDirectoryPath
Validates directory path is safe and accessible.
```csharp
public ValidationResult ValidateDirectoryPath(string? directoryPath)
```
#### Usage Example
```csharp
var validator = new InputValidator(logger);
// Validate file path
var pathValidation = validator.ValidateFilePath(userProvidedPath);
if (!pathValidation.IsValid)
{
throw new ArgumentException(pathValidation.ErrorMessage);
}
// Validate parameters
var paramValidation = validator.ValidatePluginParameters(parameters);
if (!paramValidation.IsValid)
{
return AIPluginResult.Error(paramValidation.ErrorMessage);
}
// Sanitize input
string sanitizedInput = validator.SanitizeInput(userInput);
```
---
## Analysis Plugins
### Built-in Plugins
| Plugin | Description | Key Parameters |
|--------|-------------|----------------|
| `PerformanceAnalyzerPlugin` | Performance bottleneck detection | `path`, `analyzeComplexity`, `suggestCaching` |
| `ArchitectureValidatorPlugin` | Architecture pattern validation | `projectPath`, `validateLayers`, `checkDependencies` |
| `TechnicalDebtPlugin` | Technical debt quantification | `projectPath`, `includeTests`, `calculateTrends` |
| `ComplexityAnalyzerPlugin` | Complexity metrics calculation | `path`, `includeCognitive`, `thresholds` |
| `TestAnalysisPlugin` | Test coverage and quality analysis | `testProjectPath`, `includeIntegration`, `coverageThreshold` |
| `BehaviorAnalysisPlugin` | Behavior specification analysis | `specificationPath`, `codebasePath`, `strictMode` |
| `SQLiteSchemaReaderPlugin` | Database schema analysis | `databasePath`, `analyzeIndexes`, `checkNormalization` |
### Plugin Usage Examples
#### PerformanceAnalyzerPlugin
```csharp
var parameters = new Dictionary<string, object>
{
["path"] = "src/Services/",
["analyzeComplexity"] = true,
["suggestCaching"] = true,
["analysisDepth"] = "comprehensive",
["includeMemoryAnalysis"] = true
};
var result = await plugin.ExecuteAsync(parameters, cancellationToken);
```
#### TechnicalDebtPlugin
```csharp
var parameters = new Dictionary<string, object>
{
["projectPath"] = "src/",
["includeTests"] = true,
["calculateTrends"] = true,
["debtThreshold"] = 0.1,
["prioritizeIssues"] = true
};
var result = await plugin.ExecuteAsync(parameters, cancellationToken);
```
---
## Data Models
### AggregatedResult
Aggregated results from multiple analysis plugins.
```csharp
public class AggregatedResult
{
public DateTime AnalysisDate { get; set; }
public string ProjectPath { get; set; }
public int TotalPluginsExecuted { get; set; }
public int SuccessfulPlugins { get; set; }
public int FailedPlugins { get; set; }
public TimeSpan TotalExecutionTime { get; set; }
public Dictionary<string, object> PluginResults { get; set; }
public List<AnalysisIssue> AllIssues { get; set; }
public Dictionary<string, double> QualityMetrics { get; set; }
public List<string> Recommendations { get; set; }
public OverallHealth HealthAssessment { get; set; }
}
```
### AnalysisIssue
Represents an issue found during analysis.
```csharp
public class AnalysisIssue
{
public string Source { get; set; } // Plugin that found the issue
public string Type { get; set; } // Issue category
public string Severity { get; set; } // High, Medium, Low
public string Description { get; set; } // Issue description
public string Location { get; set; } // File and line location
public string Recommendation { get; set; } // Fix recommendation
public double Impact { get; set; } // Impact score (0-10)
public double EffortToFix { get; set; } // Estimated effort
}
```
### OverallHealth
Overall health assessment of the codebase.
```csharp
public class OverallHealth
{
public double Score { get; set; } // 0-100 health score
public string Rating { get; set; } // Excellent, Good, Fair, Poor, Critical
public string Description { get; set; } // Health description
public Dictionary<string, double> ComponentScores { get; set; } // Component breakdown
}
```
### SummaryReport
Comprehensive analysis summary.
```csharp
public class SummaryReport
{
public DateTime GeneratedAt { get; set; }
public string ProjectName { get; set; }
public OverallHealth Health { get; set; }
public List<KeyFinding> KeyFindings { get; set; }
public List<PriorityAction> PriorityActions { get; set; }
public Dictionary<string, int> IssueCounts { get; set; }
public List<string> SuccessAreas { get; set; }
public string ExecutiveSummary { get; set; }
}
```
### ValidationResult
Result of input validation operation.
```csharp
public class ValidationResult
{
public bool IsValid { get; private set; }
public string? ErrorMessage { get; private set; }
public string? SanitizedValue { get; private set; }
public static ValidationResult Success(string? sanitizedValue = null);
public static ValidationResult Failure(string errorMessage);
}
```
### OperationResult&lt;T&gt;
Result wrapper for operations with error handling.
```csharp
public class OperationResult<T>
{
public bool IsSuccess { get; private set; }
public T? Value { get; private set; }
public Exception? Exception { get; private set; }
public TimeSpan Duration { get; private set; }
public string? ErrorMessage => Exception?.Message;
public static OperationResult<T> Success(T value, TimeSpan duration);
public static OperationResult<T> Failure(Exception exception, TimeSpan duration);
}
```
---
## Interfaces
### IPluginDiscovery
Interface for plugin discovery and loading.
```csharp
public interface IPluginDiscovery
{
Task<IEnumerable<IAIPlugin>> DiscoverPluginsAsync(string pluginDirectory);
Task<IAIPlugin> LoadPluginAsync(string assemblyPath, string typeName);
IEnumerable<IAIPlugin> GetBuiltInPlugins();
bool ValidatePlugin(IAIPlugin plugin);
}
```
### IAnalysisResultAggregator
Interface for result aggregation.
```csharp
public interface IAnalysisResultAggregator
{
Task<AggregatedResult> AggregateAsync(IEnumerable<AIPluginResult> results);
Task<ComparisonResult> CompareResultsAsync(AggregatedResult current, AggregatedResult previous);
Task<SummaryReport> GenerateSummaryAsync(AggregatedResult aggregatedResult);
}
```
---
## Examples
### Complete Analysis Workflow
```csharp
using MarketAlly.AIPlugin.Analysis.Infrastructure;
using MarketAlly.AIPlugin.Analysis.Plugins;
using Microsoft.Extensions.Logging;
public async Task<SummaryReport> PerformCompleteAnalysisAsync(string projectPath)
{
// Setup
var logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<Program>();
var config = new AnalysisConfiguration
{
DefaultTimeout = TimeSpan.FromMinutes(10),
MaxConcurrentAnalyses = Environment.ProcessorCount,
EnableCaching = true
};
var validator = new InputValidator(logger);
var pluginDiscovery = new PluginDiscoveryService(logger);
var resultAggregator = new AnalysisResultAggregator(logger);
var perfOptimizer = new PerformanceOptimization(logger);
// Validate inputs
var pathValidation = validator.ValidateDirectoryPath(projectPath);
if (!pathValidation.IsValid)
throw new ArgumentException(pathValidation.ErrorMessage);
// Get plugins
var plugins = pluginDiscovery.GetBuiltInPlugins();
// Prepare parameters
var parameters = new Dictionary<string, object>
{
["projectPath"] = pathValidation.SanitizedValue,
["analyzeComplexity"] = true,
["includeRecommendations"] = true,
["analysisDepth"] = "comprehensive"
};
// Execute analysis with resource management
using var context = new AnalysisContext(config, logger);
var results = new List<AIPluginResult>();
// Execute plugins in parallel
var pluginResults = await perfOptimizer.ExecuteInParallelAsync(
plugins,
async plugin => await ErrorHandling.ExecuteWithRetryAsync(
() => plugin.ExecuteAsync(parameters, context.CancellationToken),
maxRetries: 3,
logger: logger
),
maxConcurrency: config.MaxConcurrentAnalyses,
context.CancellationToken
);
results.AddRange(pluginResults);
// Aggregate results
var aggregatedResult = await resultAggregator.AggregateAsync(results);
// Generate summary
var summaryReport = await resultAggregator.GenerateSummaryAsync(aggregatedResult);
logger.LogInformation("Analysis completed: {HealthScore:F1} health score, {IssueCount} issues found",
aggregatedResult.HealthAssessment.Score, aggregatedResult.AllIssues.Count);
return summaryReport;
}
```
### Custom Plugin Development
```csharp
[AIPlugin("SecurityAnalyzer", "Analyzes code for security vulnerabilities")]
public class SecurityAnalyzerPlugin : IAIPlugin
{
private readonly ILogger<SecurityAnalyzerPlugin>? _logger;
private readonly InputValidator _validator;
public SecurityAnalyzerPlugin(ILogger<SecurityAnalyzerPlugin>? logger = null)
{
_logger = logger;
_validator = new InputValidator(logger);
}
public Dictionary<string, ParameterInfo> SupportedParameters => new()
{
["projectPath"] = new ParameterInfo { Type = typeof(string), Required = true },
["includeDependencies"] = new ParameterInfo { Type = typeof(bool), Required = false },
["securityLevel"] = new ParameterInfo { Type = typeof(string), Required = false }
};
public async Task<AIPluginResult> ExecuteAsync(Dictionary<string, object> parameters, CancellationToken cancellationToken)
{
return await ErrorHandling.SafeExecuteAsync(async () =>
{
// Validate parameters
var validation = _validator.ValidatePluginParameters(parameters);
if (!validation.IsValid)
return AIPluginResult.Error(validation.ErrorMessage);
// Extract parameters
var projectPath = parameters["projectPath"].ToString();
var includeDependencies = parameters.GetValueOrDefault("includeDependencies", false) as bool? ?? false;
var securityLevel = parameters.GetValueOrDefault("securityLevel", "standard") as string ?? "standard";
// Perform security analysis
var analysisResult = await PerformSecurityAnalysisAsync(projectPath, includeDependencies, securityLevel, cancellationToken);
return AIPluginResult.Success(analysisResult);
}, _logger);
}
private async Task<SecurityAnalysisResult> PerformSecurityAnalysisAsync(
string projectPath,
bool includeDependencies,
string securityLevel,
CancellationToken cancellationToken)
{
// Implementation here
await Task.Delay(100, cancellationToken); // Placeholder
return new SecurityAnalysisResult();
}
}
public class SecurityAnalysisResult
{
public List<SecurityIssue> SecurityIssues { get; set; } = new();
public int VulnerabilityCount { get; set; }
public string SecurityRating { get; set; } = "";
public List<string> Recommendations { get; set; } = new();
}
public class SecurityIssue
{
public string Type { get; set; } = "";
public string Severity { get; set; } = "";
public string Description { get; set; } = "";
public string Location { get; set; } = "";
public string Recommendation { get; set; } = "";
}
```
---
## Error Handling Patterns
### Recommended Error Handling
```csharp
// Pattern 1: Safe execution with result wrapper
var result = await ErrorHandling.SafeExecuteAsync(async () =>
{
return await RiskyOperationAsync();
});
if (result.IsSuccess)
{
ProcessResult(result.Value);
}
else
{
_logger.LogError(result.Exception, "Operation failed");
HandleError(result.Exception);
}
// Pattern 2: Retry with exponential backoff
var data = await ErrorHandling.ExecuteWithRetryAsync(
() => FetchDataAsync(),
maxRetries: 5,
delay: TimeSpan.FromSeconds(1),
logger: _logger
);
// Pattern 3: Timeout wrapper
var result = await ErrorHandling.WithTimeoutAsync(
token => LongRunningOperationAsync(token),
TimeSpan.FromMinutes(5),
_logger
);
```
---
## Performance Optimization Patterns
### Caching Strategies
```csharp
var perfOptimizer = new PerformanceOptimization();
// Pattern 1: Simple caching
var result = await perfOptimizer.GetOrSetCacheAsync(
"analysis_" + projectHash,
() => PerformAnalysisAsync(project),
TimeSpan.FromHours(1)
);
// Pattern 2: Parallel processing
var results = await perfOptimizer.ExecuteInParallelAsync(
files,
async file => await AnalyzeFileAsync(file),
maxConcurrency: Environment.ProcessorCount
);
// Pattern 3: Batch processing
var batchResults = await perfOptimizer.ExecuteInBatchesAsync(
items,
async batch => await ProcessBatchAsync(batch),
batchSize: 50
);
```
---
## Best Practices
### Plugin Development
1. **Always validate inputs** using `InputValidator`
2. **Use error handling patterns** with `ErrorHandling.SafeExecuteAsync`
3. **Implement proper cancellation** support
4. **Log appropriately** for debugging and monitoring
5. **Follow naming conventions** for parameters and results
### Performance Optimization
1. **Enable caching** for expensive operations
2. **Use parallel processing** for independent operations
3. **Implement object pooling** for frequently created objects
4. **Monitor memory usage** and clean up resources
5. **Use batching** for bulk operations
### Security
1. **Validate all inputs** before processing
2. **Sanitize user-provided data**
3. **Use whitelisted file extensions**
4. **Prevent path traversal attacks**
5. **Log security events** for auditing
---
**API Reference Complete**
For additional examples and advanced usage, see the [Implementation Status Report](IMPLEMENTATION_STATUS_REPORT.md) and [README](README.md).