Built Word Documents (.docx):
Spensara Overview (.docx)
Capability Assessment (.docx)
Brink POS Ingestion Analysis (.docx)
The Borg Ingestion Architecture (.docx)
Categories
Implementation Specs (157)
Detailed engineering and feature implementation specifications.
- specs/AnomalyDetection/TASK1.1.md
- specs/AnomalyDetection/TASK1.2.md
- specs/AnomalyDetection/TASK1.3.md
- specs/AnomalyDetection/TASK1.4.md
- specs/AnomalyDetection/TASK2.1.md
- specs/AnomalyDetection/TASK2.2.md
- specs/AnomalyDetection/TASK3.1.md
- specs/AnomalyDetection/TASK3.2.md
- specs/AnomalyDetection/TASK3.3.md
- specs/AnomalyDetection/TASK4.1.md
- specs/AnomalyDetection/TASK4.2.md
- specs/AnomalyDetection/TASK4.3.md
- specs/AnomalyDetection/TASK4.4.md
- specs/AnomalyDetection/TASK5.1.md
- specs/AnomalyDetection/TASK5.2.md
- specs/AnomalyDetection/TASK5.3.md
- specs/AnomalyDetection/TASK6.1.md
- specs/AnomalyDetection/TASK6.2.md
- specs/AnomalyDetection/TASK6.3.md
- specs/AnomalyDetection/TASKS.md
- specs/ApiServicePostgresTestStability/TASK.md
- specs/BackendDatabaseConsolidation/TASK.md
- specs/BackendDatabaseConsolidation/TASK1.1.md
- specs/BackendDatabaseConsolidation/TASK1.2.md
- specs/BackendDatabaseConsolidation/TASK1.3.md
- specs/BackendDatabaseConsolidation/TASK2.1.md
- specs/BackendDatabaseConsolidation/TASK2.2.md
- specs/BackendDatabaseConsolidation/TASK2.3.md
- specs/BackendDatabaseConsolidation/TASK3.1.md
- specs/BackendDatabaseConsolidation/TASK3.2.md
- specs/BackendDatabaseConsolidation/TASK4.1.md
- specs/BackendDatabaseConsolidation/TASK4.2.md
- specs/BackendDatabaseConsolidation/TASK4.3.md
- specs/BackendDatabaseConsolidation/TASK5.1.md
- specs/BackendDatabaseConsolidation/TASK5.2.md
- specs/BrinkLaborGetShiftsStaging/TASK.md
- specs/BrinkPosOrdersFirstEtlJob/TASK.md
- specs/BrinkPosOrdersFirstEtlJob/TASK0.md
- specs/BrinkPosOrdersFirstEtlJob/TASK1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.md
- specs/BrinkPosOrdersFirstEtlJob/TASK7.md
- specs/BrinkPosOrdersFirstEtlJob/TASK8.md
- specs/BrinkPosOrdersFirstEtlJob/TASK9.md
- specs/BrinkSettings2ToStaging/TASK.MD
- specs/CuratedToS3Stage/TASK.md
- specs/CuratedToS3Stage/TASK0.md
- specs/CuratedToS3Stage/TASK1.md
- specs/CuratedToS3Stage/TASK2.md
- specs/CuratedToS3Stage/TASK3.md
- specs/CuratedToS3Stage/TASK4.md
- specs/CustomerCredentialCustomHeaders/TASK.md
- specs/CustomerCredentialCustomHeaders/TASK1.1.md
- specs/CustomerCredentialCustomHeaders/TASK1.2.md
- specs/CustomerCredentialCustomHeaders/TASK2.1.md
- specs/CustomerCredentialCustomHeaders/TASK2.2.md
- specs/CustomerCredentialCustomHeaders/TASK3.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK.md
- specs/CustomerCredentialTriggeredProvisioning/TASK1.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK1.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK2.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK2.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK3.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK3.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK4.1.md
- specs/EtlSourceAdapterFramework/TASK.md
- specs/EtlSourceAdapterFramework/TASK0.md
- specs/EtlSourceAdapterFramework/TASK1.md
- specs/EtlSourceAdapterFramework/TASK2.md
- specs/EtlSourceAdapterFramework/TASK3.md
- specs/EtlSourceAdapterFramework/TASK4.md
- specs/EtlSourceAdapterFramework/TASK5.md
- specs/EtlSourceAdapterFramework/TASK6.md
- specs/KdsCdcDebeziumIntegration/TASK.md
- specs/KdsCdcDebeziumIntegration/TASK1.md
- specs/KdsCdcDebeziumIntegration/TASK2.md
- specs/KdsCdcDebeziumIntegration/TASK3.md
- specs/KdsCdcDebeziumIntegration/TASK4.md
- specs/KdsCdcDebeziumIntegration/TASK5.md
- specs/KdsCdcDurableRelationRouting/TASK.md
- specs/KdsCdcDurableRelationRouting/TASK1.md
- specs/KdsCdcDurableRelationRouting/TASK2.md
- specs/KdsCdcDurableRelationRouting/TASK3.md
- specs/KdsCdcDurableRelationRouting/TASK4.md
- specs/KdsCdcDurableRelationRouting/TASK5.md
- specs/KdsCdcIngestion/TASK.md
- specs/KdsCdcIngestion/TASK1.md
- specs/KdsCdcIngestion/TASK2.md
- specs/KdsCdcIngestion/TASK3.md
- specs/KdsCdcIngestion/TASK4.md
- specs/KdsCdcIngestion/TASK5.md
- specs/KdsCdcIngestion/TASK6.md
- specs/KdsCdcIngestion/TASK7.md
- specs/KdsCdcIngestion/TASK8.md
- specs/KdsCdcIngestion/TASK9.md
- specs/KdsCdcReplayExecution/TASK.md
- specs/KdsCdcReplayExecution/TASK1.md
- specs/KdsCdcReplayExecution/TASK2.md
- specs/KdsCdcReplayExecution/TASK3.md
- specs/KdsCdcReplayExecution/TASK4.md
- specs/KdsMonitorServingContractMigration/TASK.md
- specs/KdsMonitorServingContractMigration/TASK1.1.md
- specs/KdsMonitorServingContractMigration/TASK1.2.md
- specs/KdsMonitorServingContractMigration/TASK2.1.md
- specs/KdsMonitorServingContractMigration/TASK2.2.md
- specs/KdsMonitorServingContractMigration/TASK3.1.md
- specs/KdsMonitorServingContractMigration/TASK3.2.md
- specs/RedshiftFoundation/TASK.md
- specs/RedshiftFoundation/TASK1.1.md
- specs/RedshiftFoundation/TASK1.2.md
- specs/RedshiftFoundation/TASK1.3.md
- specs/RedshiftFoundation/TASK1.4.md
- specs/RedshiftFoundation/TASK2.1.md
- specs/RedshiftFoundation/TASK2.2.md
- specs/RedshiftFoundation/TASK3.1.md
- specs/RedshiftFoundation/TASK3.2.md
- specs/RedshiftFoundation/TASK3.3.md
- specs/RedshiftSyntheticDataFeed/TASK.md
- specs/RedshiftSyntheticDataFeed/TASK1.1.md
- specs/RedshiftSyntheticDataFeed/TASK1.2.md
- specs/RedshiftSyntheticDataFeed/TASK1.3.md
- specs/RedshiftSyntheticDataFeed/TASK2.1.md
- specs/RedshiftSyntheticDataFeed/TASK2.2.md
- specs/RedshiftSyntheticDataFeed/TASK3.1.md
- specs/RedshiftSyntheticDataFeed/TASK3.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK1.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK1.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.3.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK3.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK3.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK4.1.md
TASK 5.3: Add Enhanced CLI Options and Commands
Summary
Enhance the CLI commands with additional options for more flexible anomaly detection runs, including custom time ranges, output formats, run history, and statistics. This extends the base commands from TASK 5.1.
Dependencies
- TASK 5.1 (Base CLI commands exist)
- TASK 5.2 (DI wiring complete)
- TASK 4.4 (IMonitorRunRepository for history/stats)
Files to Modify
1. backend/Kds.Ai.Cli/Commands/AnomalyDetector/RunAnomalyDetectorCommand.cs
Add enhanced options and new commands to the existing class:
csharp
using System.Text.Json;
using ConsoleAppFramework;
using Kds.Ai.AnomalyDetector.Orchestration;
using Kds.Ai.AnomalyDetector.Repositories;
using Kds.Ai.AnomalyDetector.Seeding;
using Kds.Ai.Data.Entities;
using Kds.Ai.Data.Enums;
using Kds.Ai.Data.Repositories;
namespace Kds.Ai.Cli.Commands.AnomalyDetector;
[RegisterCommands("anomaly")]
public class RunAnomalyDetectorCommand(
IMonitorRunOrchestrator orchestrator,
IMonitorSpecRepository specRepository,
IMonitorRunRepository runRepository,
MonitorSpecSeeder seeder)
{
/// <summary>
/// Run anomaly detection with full options
/// </summary>
/// <param name="name">-n|--name, Monitor spec name</param>
/// <param name="specId">--spec-id, Monitor spec ID</param>
/// <param name="analysisTime">-t|--time, Point in time for analysis</param>
/// <param name="from">-f|--from, Analysis window start</param>
/// <param name="to">--to, Analysis window end</param>
/// <param name="dryRun">-d|--dry-run, Don't save results or emit alerts</param>
/// <param name="verbose">-v|--verbose, Show detailed progress</param>
/// <param name="outputFormat">-o|--output, Output format: text, json</param>
/// <param name="suppressAlerts">--no-alerts, Suppress alert emission</param>
/// <param name="timeout">--timeout, Execution timeout in seconds</param>
[Command("run")]
public async Task Run(
string? name = null,
Guid? specId = null,
string? analysisTime = null,
string? from = null,
string? to = null,
bool dryRun = false,
bool verbose = false,
string outputFormat = "text",
bool suppressAlerts = false,
int? timeout = null)
{
var output = CreateOutputHandler(outputFormat);
try
{
if (name is null && specId is null)
{
output.WriteError("Specify either --name (-n) or --spec-id");
return;
}
// Resolve spec
var spec = specId.HasValue
? await specRepository.GetByIdAsync(specId.Value)
: await specRepository.GetByNameAsync(name!);
if (spec is null)
{
output.WriteError(specId.HasValue
? $"Monitor spec {specId} not found"
: $"Monitor spec '{name}' not found");
return;
}
// Parse times
var time = ParseTime(analysisTime) ?? DateTimeOffset.UtcNow;
var windowStart = ParseTime(from);
var windowEnd = ParseTime(to);
output.WriteInfo($"Monitor: {spec.Name} ({spec.Id})");
output.WriteInfo($"State: {spec.State}");
output.WriteInfo($"Analysis Time: {time:yyyy-MM-dd HH:mm:ss} UTC");
// Build run options
var options = new RunOptions
{
DryRun = dryRun,
SuppressAlerts = suppressAlerts,
WindowStart = windowStart,
WindowEnd = windowEnd,
Timeout = timeout.HasValue ? TimeSpan.FromSeconds(timeout.Value) : null,
Progress = verbose ? new ConsoleProgress(output) : null
};
if (dryRun) output.WriteWarning("DRY RUN MODE");
if (suppressAlerts) output.WriteWarning("ALERTS SUPPRESSED");
// Execute
var run = await orchestrator.ExecuteAsync(spec, time, options);
// Output results
output.WriteResults(run, verbose);
}
catch (Exception ex)
{
output.WriteError(ex.Message);
if (verbose) output.WriteError(ex.ToString());
Environment.ExitCode = 1;
}
}
/// <summary>
/// Show recent run history
/// </summary>
/// <param name="specId">--spec-id, Filter by monitor spec ID</param>
/// <param name="limit">-n|--limit, Maximum runs to show</param>
/// <param name="status">--status, Filter by status: completed, failed, all</param>
/// <param name="outputFormat">-o|--output, Output format: text, json</param>
[Command("history")]
public async Task History(
Guid? specId = null,
int limit = 20,
string status = "all",
string outputFormat = "text")
{
var output = CreateOutputHandler(outputFormat);
IReadOnlyList<MonitorRun> runs;
if (specId.HasValue)
{
runs = await runRepository.GetByMonitorSpecAsync(specId.Value, limit);
}
else
{
runs = await runRepository.GetByTimeRangeAsync(
DateTimeOffset.UtcNow.AddDays(-7),
DateTimeOffset.UtcNow);
}
// Filter by status
if (status != "all")
{
var statusEnum = Enum.Parse<MonitorRunStatus>(status, ignoreCase: true);
runs = runs.Where(r => r.Status == statusEnum).ToList();
}
output.WriteRunHistory(runs.Take(limit).ToList());
}
/// <summary>
/// Show statistics for monitor runs
/// </summary>
/// <param name="days">--days, Number of days to include</param>
/// <param name="outputFormat">-o|--output, Output format: text, json</param>
[Command("stats")]
public async Task Stats(int days = 7, string outputFormat = "text")
{
var output = CreateOutputHandler(outputFormat);
var from = DateTimeOffset.UtcNow.AddDays(-days);
var stats = await runRepository.GetStatisticsAsync(from);
output.WriteStatistics(stats, days);
}
// run-all, list, create, set-state defined in TASK 5.1
// ... (see TASK 5.1 for base commands, which also don't use [Option] attributes)
// === Helper Methods ===
private static DateTimeOffset? ParseTime(string? timeStr)
{
if (string.IsNullOrEmpty(timeStr)) return null;
// Support relative times
if (timeStr.StartsWith("-"))
{
if (int.TryParse(timeStr[1..^1], out var value))
{
return timeStr[^1] switch
{
'h' => DateTimeOffset.UtcNow.AddHours(-value),
'd' => DateTimeOffset.UtcNow.AddDays(-value),
'm' => DateTimeOffset.UtcNow.AddMinutes(-value),
_ => throw new ArgumentException($"Unknown time unit: {timeStr}")
};
}
}
if (DateTimeOffset.TryParse(timeStr, out var result))
return result;
throw new ArgumentException($"Invalid time format: {timeStr}");
}
private IOutputHandler CreateOutputHandler(string format) => format.ToLower() switch
{
"json" => new JsonOutputHandler(),
_ => new TextOutputHandler()
};
private sealed class ConsoleProgress(IOutputHandler output) : IProgress<RunProgress>
{
public void Report(RunProgress value)
{
var prefix = value.Phase switch
{
RunPhase.Completed => "[DONE]",
RunPhase.Failed => "[FAIL]",
_ => "[....]"
};
output.WriteInfo($"{prefix} {value.Phase}: {value.Message}");
}
}
}
// === Output Handlers ===
public interface IOutputHandler
{
void WriteInfo(string message);
void WriteWarning(string message);
void WriteError(string message);
void WriteResults(MonitorRun run, bool verbose);
void WriteRunHistory(IReadOnlyList<MonitorRun> runs);
void WriteStatistics(RunStatistics stats, int days);
}
internal sealed class TextOutputHandler : IOutputHandler
{
public void WriteInfo(string message) => Console.WriteLine(message);
public void WriteWarning(string message) => Console.WriteLine($"[WARN] {message}");
public void WriteError(string message) => Console.WriteLine($"[ERROR] {message}");
public void WriteResults(MonitorRun run, bool verbose)
{
Console.WriteLine();
Console.WriteLine("=== Results ===");
Console.WriteLine($"Run ID: {run.Id}");
Console.WriteLine($"Status: {run.Status}");
Console.WriteLine($"Duration: {run.DurationMs}ms");
Console.WriteLine($"Anomalies: {run.AnomaliesDetected}");
if (run.AlertEvents.Count > 0)
{
Console.WriteLine($"Alerts: {run.AlertEvents.Count}");
foreach (var alert in run.AlertEvents)
{
Console.WriteLine($" - {alert.Id} ({alert.Severity})");
}
}
if (!string.IsNullOrEmpty(run.ErrorMessage))
Console.WriteLine($"Error: {run.ErrorMessage}");
if (verbose && !string.IsNullOrEmpty(run.ResultSummary))
{
Console.WriteLine();
Console.WriteLine("=== Details ===");
Console.WriteLine(run.ResultSummary);
}
}
public void WriteRunHistory(IReadOnlyList<MonitorRun> runs)
{
Console.WriteLine($"{"ID",-36} {"Status",-10} {"Anomalies",-10} {"Started",-20}");
Console.WriteLine(new string('-', 80));
foreach (var run in runs)
{
Console.WriteLine($"{run.Id,-36} {run.Status,-10} {run.AnomaliesDetected,-10} {run.StartedAt:yyyy-MM-dd HH:mm}");
}
}
public void WriteStatistics(RunStatistics stats, int days)
{
Console.WriteLine($"Statistics for last {days} days:");
Console.WriteLine($" Total Runs: {stats.TotalRuns}");
Console.WriteLine($" Completed: {stats.CompletedRuns}");
Console.WriteLine($" Failed: {stats.FailedRuns}");
Console.WriteLine($" With Anomalies: {stats.RunsWithAnomalies}");
Console.WriteLine($" Total Anomalies: {stats.TotalAnomaliesDetected}");
Console.WriteLine($" Avg Duration: {stats.AverageRunDurationMs:F0}ms");
Console.WriteLine($" Total Tokens: {stats.TotalTokensUsed}");
}
}
internal sealed class JsonOutputHandler : IOutputHandler
{
public void WriteInfo(string message) { }
public void WriteWarning(string message) { }
public void WriteError(string message) => WriteJson(new { error = message });
public void WriteResults(MonitorRun run, bool verbose) =>
WriteJson(new
{
runId = run.Id,
status = run.Status.ToString(),
durationMs = run.DurationMs,
anomaliesDetected = run.AnomaliesDetected,
alertCount = run.AlertEvents.Count,
error = run.ErrorMessage,
resultSummary = verbose ? run.ResultSummary : null
});
public void WriteRunHistory(IReadOnlyList<MonitorRun> runs) =>
WriteJson(runs.Select(r => new
{
id = r.Id,
status = r.Status.ToString(),
anomalies = r.AnomaliesDetected,
started = r.StartedAt
}));
public void WriteStatistics(RunStatistics stats, int days) =>
WriteJson(new { days, stats });
private static void WriteJson(object obj) =>
Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions
{
WriteIndented = true
}));
}Enhanced Command Options Summary
anomaly run Options
| Option | Short | Description |
|---|---|---|
--name | -n | Monitor spec name |
--spec-id | Monitor spec ID | |
--time | -t | Analysis point in time (ISO or relative) |
--from | -f | Window start time |
--to | Window end time | |
--dry-run | -d | Don't persist results |
--verbose | -v | Show progress |
--output | -o | Output format (text/json) |
--no-alerts | Suppress alerts | |
--timeout | Timeout in seconds |
Relative Time Syntax
bash
# 2 hours ago
anomaly run -n "Monitor Name" -t "-2h"
# 1 day ago
anomaly run -n "Monitor Name" -t "-1d"
# Custom range
anomaly run -n "Monitor Name" -f "-24h" --to "-1h"Changes from Old Design
- Removed
--storeoption entirely (MonitorSpec has no StoreGuid) - Removed
--skip-preflight(preflight was domain-specific, agent decides) - Removed
--thresholdand--lookbackconfig overrides (noPrepTimeMonitorConfigtyped config) - Removed
ApplyConfigOverrideshelper (no typed config to deserialize) - Removed
togglecommand with--enable/--disable(replaced byset-statein TASK 5.1) historycommand filters by--spec-idinstead of--store- All output handlers use
run.AlertEventscollection instead ofrun.AlertEventId - Uses
run.AlertEvents.Countinstead ofrun.AlertEventId.HasValue - No
GetByStoreAsynccalls anywhere
Acceptance Criteria
- [ ] All new options added to
runcommand - [ ]
historycommand shows run history (by spec, by time range) - [ ]
statscommand shows statistics - [ ] JSON output format works correctly
- [ ] Relative time parsing (
-2h,-1d) works - [ ] No
[Option]attributes used (deprecated in ConsoleAppFramework v5) - [ ] Aliases defined in XML doc comments (e.g.,
-n|--limitin<param>tag) - [ ] No references to
--store,--skip-preflight,--threshold,--lookback - [ ] No
PrepTimeMonitorConfigdeserialization - [ ] Uses
run.AlertEvents(collection) notrun.AlertEventId(single FK) - [ ] Uses
spec.State(notspec.IsEnabled) - [ ] No
GetByStoreAsynccalls - [ ] Primary constructor used for
RunAnomalyDetectorCommand(no_fieldpattern) - [ ] No inline fully-qualified types (
Data.Entities.MonitorRun) — usesusingdirectives - [ ]
IMonitorSpecRepositoryimported fromKds.Ai.Data.Repositories - [ ]
ConsoleProgress,TextOutputHandler,JsonOutputHandleraresealed - [ ] Implementation classes use
internal sealed(interfaces staypublic) - [ ] Solution builds without errors
Notes
IMonitorRunRepositoryinjected for history/stats commands- No
[Option]attributes needed - parameter names automatically become options - Aliases are defined in XML doc comments using pipe separator (e.g.,
-o|--output) - ConsoleAppFramework v5 deprecated
[Option]in favor of automatic option generation - Consider adding
--quietoption for scripting - Consider adding shell completion scripts
- The output handlers can be extended with more formats later