diff --git a/reports/current.md b/reports/current.md index 50cdb85..96cd3f4 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-27 10:17:36 UTC +Generated: 2026-02-27 10:18:29 UTC ## Modules (12 total) diff --git a/reports/report_0436e08.md b/reports/report_0436e08.md new file mode 100644 index 0000000..96cd3f4 --- /dev/null +++ b/reports/report_0436e08.md @@ -0,0 +1,35 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-27 10:18:29 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| verified | 12 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| unknown | 3394 | +| verified | 279 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| deferred | 2680 | +| n_a | 187 | +| verified | 390 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**868/6942 items complete (12.5%)** diff --git a/tools/NatsNet.PortTracker/Commands/AuditCommand.cs b/tools/NatsNet.PortTracker/Commands/AuditCommand.cs new file mode 100644 index 0000000..494520d --- /dev/null +++ b/tools/NatsNet.PortTracker/Commands/AuditCommand.cs @@ -0,0 +1,216 @@ +using System.CommandLine; +using System.Text; +using NatsNet.PortTracker.Audit; +using NatsNet.PortTracker.Data; + +namespace NatsNet.PortTracker.Commands; + +public static class AuditCommand +{ + public static Command Create(Option dbOption) + { + var sourceOpt = new Option("--source") + { + Description = "Path to the .NET source directory", + DefaultValueFactory = _ => Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "src", "ZB.MOM.NatsNet.Server") + }; + + var outputOpt = new Option("--output") + { + Description = "CSV report output path", + DefaultValueFactory = _ => Path.Combine(Directory.GetCurrentDirectory(), "reports", "audit-results.csv") + }; + + var moduleOpt = new Option("--module") + { + Description = "Restrict to a specific module ID" + }; + + var executeOpt = new Option("--execute") + { + Description = "Apply DB updates (default: dry-run preview)", + DefaultValueFactory = _ => false + }; + + var cmd = new Command("audit", "Classify unknown features by inspecting .NET source code"); + cmd.Add(sourceOpt); + cmd.Add(outputOpt); + cmd.Add(moduleOpt); + cmd.Add(executeOpt); + + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + var sourcePath = parseResult.GetValue(sourceOpt)!; + var outputPath = parseResult.GetValue(outputOpt)!; + var moduleId = parseResult.GetValue(moduleOpt); + var execute = parseResult.GetValue(executeOpt); + + RunAudit(dbPath, sourcePath, outputPath, moduleId, execute); + }); + + return cmd; + } + + private static void RunAudit(string dbPath, string sourcePath, string outputPath, int? moduleId, bool execute) + { + // Validate source directory + if (!Directory.Exists(sourcePath)) + { + Console.WriteLine($"Error: source directory not found: {sourcePath}"); + return; + } + + // 1. Build source index + Console.WriteLine($"Parsing .NET source files in {sourcePath}..."); + var indexer = new SourceIndexer(); + indexer.IndexDirectory(sourcePath); + Console.WriteLine($"Indexed {indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods/properties."); + + // 2. Query unknown features + using var db = new Database(dbPath); + var sql = "SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM features WHERE status = 'unknown'"; + var parameters = new List<(string, object?)>(); + if (moduleId is not null) + { + sql += " AND module_id = @module"; + parameters.Add(("@module", moduleId)); + } + sql += " ORDER BY id"; + + var rows = db.Query(sql, parameters.ToArray()); + if (rows.Count == 0) + { + Console.WriteLine("No unknown features found."); + return; + } + Console.WriteLine($"Found {rows.Count} unknown features to classify.\n"); + + // 3. Classify each feature + var classifier = new FeatureClassifier(indexer); + var results = new List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)>(); + + foreach (var row in rows) + { + var feature = new FeatureClassifier.FeatureRecord( + Id: Convert.ToInt64(row["id"]), + DotnetClass: row["dotnet_class"]?.ToString() ?? "", + DotnetMethod: row["dotnet_method"]?.ToString() ?? "", + GoFile: row["go_file"]?.ToString() ?? "", + GoMethod: row["go_method"]?.ToString() ?? ""); + + var result = classifier.Classify(feature); + results.Add((feature, result)); + } + + // 4. Write CSV report + WriteCsvReport(outputPath, results); + + // 5. Print console summary + var grouped = results.GroupBy(r => r.Result.Status) + .ToDictionary(g => g.Key, g => g.Count()); + + Console.WriteLine("Feature Status Audit Results"); + Console.WriteLine("============================="); + Console.WriteLine($"Source: {sourcePath} ({indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods indexed)"); + Console.WriteLine($"Features audited: {results.Count}"); + Console.WriteLine(); + Console.WriteLine($" verified: {grouped.GetValueOrDefault("verified", 0)}"); + Console.WriteLine($" stub: {grouped.GetValueOrDefault("stub", 0)}"); + Console.WriteLine($" n_a: {grouped.GetValueOrDefault("n_a", 0)}"); + Console.WriteLine($" deferred: {grouped.GetValueOrDefault("deferred", 0)}"); + Console.WriteLine(); + + if (!execute) + { + Console.WriteLine("Dry-run mode. Add --execute to apply changes."); + Console.WriteLine($"Report: {outputPath}"); + return; + } + + // 6. Apply DB updates + ApplyUpdates(db, results); + Console.WriteLine($"Report: {outputPath}"); + } + + private static void WriteCsvReport( + string outputPath, + List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results) + { + // Ensure directory exists + var dir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + + var sb = new StringBuilder(); + sb.AppendLine("id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason"); + foreach (var (feature, result) in results) + { + sb.AppendLine($"{feature.Id},{CsvEscape(feature.DotnetClass)},{CsvEscape(feature.DotnetMethod)},{CsvEscape(feature.GoFile)},{CsvEscape(feature.GoMethod)},unknown,{result.Status},{CsvEscape(result.Reason)}"); + } + File.WriteAllText(outputPath, sb.ToString()); + } + + private static void ApplyUpdates( + Database db, + List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results) + { + // Group by (status, notes) for efficient batch updates + var groups = results + .GroupBy(r => (r.Result.Status, Notes: r.Result.Status == "n_a" ? r.Result.Reason : (string?)null)) + .ToList(); + + var totalUpdated = 0; + using var transaction = db.Connection.BeginTransaction(); + try + { + foreach (var group in groups) + { + var ids = group.Select(r => r.Feature.Id).ToList(); + var status = group.Key.Status; + var notes = group.Key.Notes; + + // Build parameterized IN clause + var placeholders = new List(); + using var cmd = db.CreateCommand(""); + for (var i = 0; i < ids.Count; i++) + { + placeholders.Add($"@id{i}"); + cmd.Parameters.AddWithValue($"@id{i}", ids[i]); + } + + cmd.Parameters.AddWithValue("@status", status); + + if (notes is not null) + { + cmd.CommandText = $"UPDATE features SET status = @status, notes = @notes WHERE id IN ({string.Join(", ", placeholders)})"; + cmd.Parameters.AddWithValue("@notes", notes); + } + else + { + cmd.CommandText = $"UPDATE features SET status = @status WHERE id IN ({string.Join(", ", placeholders)})"; + } + + cmd.Transaction = transaction; + var affected = cmd.ExecuteNonQuery(); + totalUpdated += affected; + } + + transaction.Commit(); + Console.WriteLine($"Updated {totalUpdated} features."); + } + catch + { + transaction.Rollback(); + Console.WriteLine("Error: transaction rolled back."); + throw; + } + } + + private static string CsvEscape(string value) + { + if (value.Contains(',') || value.Contains('"') || value.Contains('\n')) + return $"\"{value.Replace("\"", "\"\"")}\""; + return value; + } +} diff --git a/tools/NatsNet.PortTracker/Program.cs b/tools/NatsNet.PortTracker/Program.cs index 24d4624..1c943f8 100644 --- a/tools/NatsNet.PortTracker/Program.cs +++ b/tools/NatsNet.PortTracker/Program.cs @@ -39,6 +39,7 @@ rootCommand.Add(LibraryCommands.Create(dbOption, schemaOption)); rootCommand.Add(DependencyCommands.Create(dbOption, schemaOption)); rootCommand.Add(ReportCommands.Create(dbOption, schemaOption)); rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption)); +rootCommand.Add(AuditCommand.Create(dbOption)); var parseResult = rootCommand.Parse(args); return await parseResult.InvokeAsync();