From c5e0416793b0c7d595c2c32273c624b5bed49350 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 27 Feb 2026 13:02:43 -0500 Subject: [PATCH] Add batch commands to PortTracker CLI and migrate batch tables into porting.db Implements batch list/show/ready/next/start/complete commands with dependency validation, migrates 42 implementation batches (2377 features, 2087 tests) from porting_batches.db into the live tracking database, and documents the batch workflow in AGENTS.md. --- AGENTS.md | 44 ++ porting-schema.sql | 29 ++ porting.db | Bin 6107136 -> 6320128 bytes reports/current.md | 2 +- reports/report_fe3fd7c.md | 37 ++ .../Commands/BatchCommands.cs | 454 ++++++++++++++++++ tools/NatsNet.PortTracker/Program.cs | 1 + 7 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 reports/report_fe3fd7c.md create mode 100644 tools/NatsNet.PortTracker/Commands/BatchCommands.cs diff --git a/AGENTS.md b/AGENTS.md index 198fb29..e990b6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,6 +92,18 @@ dotnet run --project tools/NatsNet.PortTracker -- --db porting.db | `test update --status ` | Update one test | | `test batch-update --ids "1-10" --set-status --execute` | Bulk update tests | | `module update --status ` | Update module status | +| `batch start ` | Mark batch as in-progress (validates deps) | +| `batch complete ` | Mark batch as complete (validates all items done) | + +### Batch Querying + +| Command | Purpose | +|---------|---------| +| `batch list` | List all implementation batches | +| `batch list --status ` | Filter batches by status | +| `batch show ` | Show batch details with features and tests | +| `batch ready` | List batches ready to start (deps met) | +| `batch next` | Show next recommended batch | ### Audit Verification @@ -147,6 +159,38 @@ dotnet run --project tools/NatsNet.PortTracker -- test list --status stub --db p dotnet run --project tools/NatsNet.PortTracker -- test list --status deferred --db porting.db ``` +### Batch Workflow + +Work is organized into 42 implementation batches with dependency ordering. Use batches to find and track work: + +1. **Find the next batch** — `batch next` returns the lowest-priority ready batch: + +```bash +dotnet run --project tools/NatsNet.PortTracker -- batch next --db porting.db +``` + +2. **Start it** — marks the batch as in-progress (validates dependencies are met): + +```bash +dotnet run --project tools/NatsNet.PortTracker -- batch start --db porting.db +``` + +3. **See all items** — lists every feature and test in the batch: + +```bash +dotnet run --project tools/NatsNet.PortTracker -- batch show --db porting.db +``` + +4. **Implement features first**, then write/port the tests. + +5. **Complete it** — validates all features and tests are verified/complete/n_a: + +```bash +dotnet run --project tools/NatsNet.PortTracker -- batch complete --db porting.db +``` + +The system enforces dependency ordering — you cannot start a batch until all batches it depends on are complete. Use `batch ready` to see all currently available batches. + ### Implementing a Feature 1. **Claim it** — mark as stub before starting: diff --git a/porting-schema.sql b/porting-schema.sql index f016122..b026a87 100644 --- a/porting-schema.sql +++ b/porting-schema.sql @@ -99,6 +99,31 @@ CREATE TABLE IF NOT EXISTS status_overrides ( created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE IF NOT EXISTS implementation_batches ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + priority INTEGER NOT NULL, + feature_count INTEGER DEFAULT 0, + test_count INTEGER DEFAULT 0, + status TEXT DEFAULT 'pending', + depends_on TEXT, + go_files TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS batch_features ( + batch_id INTEGER NOT NULL REFERENCES implementation_batches(id), + feature_id INTEGER NOT NULL REFERENCES features(id), + PRIMARY KEY (batch_id, feature_id) +); + +CREATE TABLE IF NOT EXISTS batch_tests ( + batch_id INTEGER NOT NULL REFERENCES implementation_batches(id), + test_id INTEGER NOT NULL REFERENCES unit_tests(id), + PRIMARY KEY (batch_id, test_id) +); + -- Indexes CREATE INDEX IF NOT EXISTS idx_features_module ON features(module_id); CREATE INDEX IF NOT EXISTS idx_features_status ON features(status); @@ -109,6 +134,10 @@ CREATE INDEX IF NOT EXISTS idx_deps_source ON dependencies(source_type, source_i CREATE INDEX IF NOT EXISTS idx_deps_target ON dependencies(target_type, target_id); CREATE INDEX IF NOT EXISTS idx_library_status ON library_mappings(status); CREATE INDEX IF NOT EXISTS idx_modules_status ON modules(status); +CREATE INDEX IF NOT EXISTS idx_batch_features_feature ON batch_features(feature_id); +CREATE INDEX IF NOT EXISTS idx_batch_tests_test ON batch_tests(test_id); +CREATE INDEX IF NOT EXISTS idx_batches_status ON implementation_batches(status); +CREATE INDEX IF NOT EXISTS idx_batches_priority ON implementation_batches(priority); -- Triggers to auto-update updated_at CREATE TRIGGER IF NOT EXISTS trg_modules_updated AFTER UPDATE ON modules diff --git a/porting.db b/porting.db index d7dc49e81f8753c4fbcdfdb82e341d506bfc9344..c5e2d1cdf57be332eb250af8d1efec68f782ec97 100644 GIT binary patch delta 146504 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 hVfexL>b0! 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

ho)A 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 O%+z>vSmV$kjr|S5v_E+O delta 555 zcmYL@J4{ny9L2x?*WR}Gw%6XGSOgz{1+6@M)IOxB#|KjIf%pUkUqK~7+>CLn3@45l z5qFAzBDjA`hG;=U(iko>)I& z8mGf|eXpXI#s8j>$Kv0}%MWdwTKQ(IqKsO~{;5W*!eM$6hWqv2+ zT`>ArJ*o0X;jz|GGT)?U-b;%a$jO{3b;)92O=fP-=3bkTtbFvxYh+dvLac3o2oe?% zBEm$3unC8V5;4LhHV|>bBN9Xnv60wB)DoMCBvD6fA?k?+Vk@zY*iP&q8i^*Nnb=9B zh!&!i*hTCn(!?I3jc6x2h)!ZJv5(kK93V2pK_W}!h&)js4iQ~Mk?1B46Gw=n1c+nA zapDAVlIS5$5xqo-=p*`x)5IC#EOCw)AkGtm#0BCaaf!H0Tp_L!L&Pv~jkrFCkzfA- D#9XAk diff --git a/reports/current.md b/reports/current.md index accf510..df8ed34 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-27 17:42:32 UTC +Generated: 2026-02-27 18:02:44 UTC ## Modules (12 total) diff --git a/reports/report_fe3fd7c.md b/reports/report_fe3fd7c.md new file mode 100644 index 0000000..df8ed34 --- /dev/null +++ b/reports/report_fe3fd7c.md @@ -0,0 +1,37 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-27 18:02:44 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| verified | 12 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| deferred | 2377 | +| n_a | 24 | +| stub | 1 | +| verified | 1271 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| deferred | 2640 | +| n_a | 187 | +| verified | 430 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**1924/6942 items complete (27.7%)** diff --git a/tools/NatsNet.PortTracker/Commands/BatchCommands.cs b/tools/NatsNet.PortTracker/Commands/BatchCommands.cs new file mode 100644 index 0000000..8a614a8 --- /dev/null +++ b/tools/NatsNet.PortTracker/Commands/BatchCommands.cs @@ -0,0 +1,454 @@ +using System.CommandLine; +using NatsNet.PortTracker.Data; + +namespace NatsNet.PortTracker.Commands; + +public static class BatchCommands +{ + public static Command Create(Option dbOption) + { + var batchCommand = new Command("batch", "Manage implementation batches"); + + batchCommand.Add(CreateList(dbOption)); + batchCommand.Add(CreateShow(dbOption)); + batchCommand.Add(CreateReady(dbOption)); + batchCommand.Add(CreateNext(dbOption)); + batchCommand.Add(CreateStart(dbOption)); + batchCommand.Add(CreateComplete(dbOption)); + + return batchCommand; + } + + private static Command CreateList(Option dbOption) + { + var statusOpt = new Option("--status") + { + Description = "Filter by status: pending, in_progress, complete" + }; + + var cmd = new Command("list", "List all implementation batches"); + cmd.Add(statusOpt); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + var status = parseResult.GetValue(statusOpt); + using var db = new Database(dbPath); + + var sql = "SELECT id, name, feature_count, test_count, status, depends_on FROM implementation_batches"; + var parameters = new List<(string, object?)>(); + + if (status is not null) + { + sql += " WHERE status = @status"; + parameters.Add(("@status", status)); + } + sql += " ORDER BY priority, id"; + + var rows = db.Query(sql, parameters.ToArray()); + if (rows.Count == 0) + { + Console.WriteLine("No batches found."); + return; + } + + Console.WriteLine($"{"ID",-4} {"Name",-45} {"Feat",-5} {"Test",-5} {"Status",-12} {"Depends On"}"); + Console.WriteLine(new string('-', 100)); + foreach (var row in rows) + { + Console.WriteLine( + $"{row["id"],-4} " + + $"{Truncate(row["name"]?.ToString(), 44),-45} " + + $"{row["feature_count"],-5} " + + $"{row["test_count"],-5} " + + $"{row["status"],-12} " + + $"{row["depends_on"] ?? ""}"); + } + Console.WriteLine($"\nTotal: {rows.Count} batches"); + }); + + return cmd; + } + + private static Command CreateShow(Option dbOption) + { + var idArg = new Argument("id") { Description = "Batch ID to show" }; + + var cmd = new Command("show", "Show batch details with features and tests"); + cmd.Add(idArg); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + var id = parseResult.GetValue(idArg); + using var db = new Database(dbPath); + + var batches = db.Query( + "SELECT * FROM implementation_batches WHERE id = @id", + ("@id", id)); + + if (batches.Count == 0) + { + Console.WriteLine($"Batch {id} not found."); + return; + } + + var batch = batches[0]; + Console.WriteLine($"Batch {batch["id"]}: {batch["name"]}"); + Console.WriteLine($" Description: {batch["description"] ?? "(none)"}"); + Console.WriteLine($" Priority: {batch["priority"]}"); + Console.WriteLine($" Status: {batch["status"]}"); + Console.WriteLine($" Depends On: {batch["depends_on"] ?? "(none)"}"); + Console.WriteLine($" Go Files: {batch["go_files"] ?? "(none)"}"); + Console.WriteLine($" Features: {batch["feature_count"]}"); + Console.WriteLine($" Tests: {batch["test_count"]}"); + Console.WriteLine(); + + // Show features + var features = db.Query( + """ + SELECT f.id, f.name, f.status, f.go_file + FROM batch_features bf + JOIN features f ON f.id = bf.feature_id + WHERE bf.batch_id = @id + ORDER BY f.id + """, + ("@id", id)); + + if (features.Count > 0) + { + Console.WriteLine($"Features ({features.Count}):"); + Console.WriteLine($" {"ID",-6} {"Status",-10} {"Name",-50} {"Go File"}"); + Console.WriteLine(" " + new string('-', 90)); + foreach (var f in features) + { + Console.WriteLine( + $" {f["id"],-6} " + + $"{f["status"],-10} " + + $"{Truncate(f["name"]?.ToString(), 49),-50} " + + $"{Truncate(f["go_file"]?.ToString(), 30)}"); + } + } + + Console.WriteLine(); + + // Show tests + var tests = db.Query( + """ + SELECT t.id, t.name, t.status, t.go_file + FROM batch_tests bt + JOIN unit_tests t ON t.id = bt.test_id + WHERE bt.batch_id = @id + ORDER BY t.id + """, + ("@id", id)); + + if (tests.Count > 0) + { + Console.WriteLine($"Tests ({tests.Count}):"); + Console.WriteLine($" {"ID",-6} {"Status",-10} {"Name",-50} {"Go File"}"); + Console.WriteLine(" " + new string('-', 90)); + foreach (var t in tests) + { + Console.WriteLine( + $" {t["id"],-6} " + + $"{t["status"],-10} " + + $"{Truncate(t["name"]?.ToString(), 49),-50} " + + $"{Truncate(t["go_file"]?.ToString(), 30)}"); + } + } + }); + + return cmd; + } + + private static Command CreateReady(Option dbOption) + { + var cmd = new Command("ready", "List batches ready to start (all dependencies complete)"); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + using var db = new Database(dbPath); + + var readyBatches = GetReadyBatches(db); + if (readyBatches.Count == 0) + { + Console.WriteLine("No batches ready."); + return; + } + + Console.WriteLine($"{"ID",-4} {"Name",-45} {"Feat",-5} {"Test",-5} {"Priority"}"); + Console.WriteLine(new string('-', 70)); + foreach (var row in readyBatches) + { + Console.WriteLine( + $"{row["id"],-4} " + + $"{Truncate(row["name"]?.ToString(), 44),-45} " + + $"{row["feature_count"],-5} " + + $"{row["test_count"],-5} " + + $"{row["priority"]}"); + } + Console.WriteLine($"\n{readyBatches.Count} batches ready"); + }); + + return cmd; + } + + private static Command CreateNext(Option dbOption) + { + var cmd = new Command("next", "Show the next recommended batch (lowest priority ready)"); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + using var db = new Database(dbPath); + + var readyBatches = GetReadyBatches(db); + if (readyBatches.Count == 0) + { + Console.WriteLine("No batches ready."); + return; + } + + var next = readyBatches[0]; // Already sorted by priority + Console.WriteLine($"Next batch: #{next["id"]} — {next["name"]}"); + Console.WriteLine($" Priority: {next["priority"]}"); + Console.WriteLine($" Features: {next["feature_count"]}"); + Console.WriteLine($" Tests: {next["test_count"]}"); + Console.WriteLine($" Depends On: {next["depends_on"] ?? "(none)"}"); + Console.WriteLine($"\nRun: batch start {next["id"]}"); + }); + + return cmd; + } + + private static Command CreateStart(Option dbOption) + { + var idArg = new Argument("id") { Description = "Batch ID to start" }; + + var cmd = new Command("start", "Mark batch as in-progress (validates dependencies)"); + cmd.Add(idArg); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + var id = parseResult.GetValue(idArg); + using var db = new Database(dbPath); + + var batches = db.Query( + "SELECT * FROM implementation_batches WHERE id = @id", + ("@id", id)); + + if (batches.Count == 0) + { + Console.WriteLine($"Batch {id} not found."); + return; + } + + var batch = batches[0]; + var status = batch["status"]?.ToString(); + + if (status == "complete") + { + Console.WriteLine($"Batch {id} is already complete."); + return; + } + + if (status == "in_progress") + { + Console.WriteLine($"Batch {id} is already in progress."); + return; + } + + // Check dependencies + if (!AreDependenciesMet(db, batch)) + { + var deps = batch["depends_on"]?.ToString() ?? ""; + Console.WriteLine($"Cannot start batch {id}: dependencies not met."); + Console.WriteLine($" Depends on: {deps}"); + PrintDependencyStatus(db, deps); + return; + } + + db.Execute( + "UPDATE implementation_batches SET status = 'in_progress' WHERE id = @id", + ("@id", id)); + + Console.WriteLine($"Batch {id} started: {batch["name"]}"); + Console.WriteLine($" Features: {batch["feature_count"]}, Tests: {batch["test_count"]}"); + Console.WriteLine($"\nRun: batch show {id} — to see all items"); + }); + + return cmd; + } + + private static Command CreateComplete(Option dbOption) + { + var idArg = new Argument("id") { Description = "Batch ID to complete" }; + + var cmd = new Command("complete", "Mark batch as complete (validates all items done)"); + cmd.Add(idArg); + cmd.SetAction(parseResult => + { + var dbPath = parseResult.GetValue(dbOption)!; + var id = parseResult.GetValue(idArg); + using var db = new Database(dbPath); + + var batches = db.Query( + "SELECT * FROM implementation_batches WHERE id = @id", + ("@id", id)); + + if (batches.Count == 0) + { + Console.WriteLine($"Batch {id} not found."); + return; + } + + var batch = batches[0]; + if (batch["status"]?.ToString() == "complete") + { + Console.WriteLine($"Batch {id} is already complete."); + return; + } + + // Check all features are done + var incompleteFeatures = db.Query( + """ + SELECT f.id, f.name, f.status + FROM batch_features bf + JOIN features f ON f.id = bf.feature_id + WHERE bf.batch_id = @id AND f.status NOT IN ('verified', 'complete', 'n_a') + ORDER BY f.id + """, + ("@id", id)); + + // Check all tests are done + var incompleteTests = db.Query( + """ + SELECT t.id, t.name, t.status + FROM batch_tests bt + JOIN unit_tests t ON t.id = bt.test_id + WHERE bt.batch_id = @id AND t.status NOT IN ('verified', 'complete', 'n_a') + ORDER BY t.id + """, + ("@id", id)); + + if (incompleteFeatures.Count > 0 || incompleteTests.Count > 0) + { + Console.WriteLine($"Cannot complete batch {id}: items remain."); + + if (incompleteFeatures.Count > 0) + { + Console.WriteLine($"\n Incomplete features ({incompleteFeatures.Count}):"); + foreach (var f in incompleteFeatures.Take(20)) + { + Console.WriteLine($" {f["id"],-6} {f["status"],-10} {Truncate(f["name"]?.ToString(), 50)}"); + } + if (incompleteFeatures.Count > 20) + Console.WriteLine($" ... and {incompleteFeatures.Count - 20} more"); + } + + if (incompleteTests.Count > 0) + { + Console.WriteLine($"\n Incomplete tests ({incompleteTests.Count}):"); + foreach (var t in incompleteTests.Take(20)) + { + Console.WriteLine($" {t["id"],-6} {t["status"],-10} {Truncate(t["name"]?.ToString(), 50)}"); + } + if (incompleteTests.Count > 20) + Console.WriteLine($" ... and {incompleteTests.Count - 20} more"); + } + return; + } + + db.Execute( + "UPDATE implementation_batches SET status = 'complete' WHERE id = @id", + ("@id", id)); + + Console.WriteLine($"Batch {id} completed: {batch["name"]}"); + }); + + return cmd; + } + + private static List> GetReadyBatches(Database db) + { + // Get all pending batches + var pending = db.Query( + "SELECT * FROM implementation_batches WHERE status = 'pending' ORDER BY priority, id"); + + // Get statuses of all batches for dependency checking + var allStatuses = db.Query("SELECT id, status FROM implementation_batches"); + var statusMap = new Dictionary(); + foreach (var row in allStatuses) + { + statusMap[(long)row["id"]!] = row["status"]?.ToString() ?? "pending"; + } + + var ready = new List>(); + foreach (var batch in pending) + { + if (AreDependenciesMet(statusMap, batch["depends_on"]?.ToString())) + ready.Add(batch); + } + + return ready; + } + + private static bool AreDependenciesMet(Database db, Dictionary batch) + { + var depsStr = batch["depends_on"]?.ToString(); + if (string.IsNullOrWhiteSpace(depsStr)) + return true; + + var allStatuses = db.Query("SELECT id, status FROM implementation_batches"); + var statusMap = new Dictionary(); + foreach (var row in allStatuses) + { + statusMap[(long)row["id"]!] = row["status"]?.ToString() ?? "pending"; + } + + return AreDependenciesMet(statusMap, depsStr); + } + + private static bool AreDependenciesMet(Dictionary statusMap, string? depsStr) + { + if (string.IsNullOrWhiteSpace(depsStr)) + return true; + + var depIds = depsStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var depIdStr in depIds) + { + if (long.TryParse(depIdStr, out var depId)) + { + if (!statusMap.TryGetValue(depId, out var depStatus) || depStatus != "complete") + return false; + } + } + + return true; + } + + private static void PrintDependencyStatus(Database db, string depsStr) + { + if (string.IsNullOrWhiteSpace(depsStr)) return; + + var depIds = depsStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var depIdStr in depIds) + { + if (int.TryParse(depIdStr, out var depId)) + { + var rows = db.Query( + "SELECT id, name, status FROM implementation_batches WHERE id = @id", + ("@id", depId)); + if (rows.Count > 0) + { + var r = rows[0]; + Console.WriteLine($" Batch {r["id"]}: {r["status"]} — {r["name"]}"); + } + } + } + } + + private static string Truncate(string? s, int maxLen) + { + if (s is null) return ""; + return s.Length <= maxLen ? s : s[..(maxLen - 2)] + ".."; + } +} diff --git a/tools/NatsNet.PortTracker/Program.cs b/tools/NatsNet.PortTracker/Program.cs index 49f1a8d..333f712 100644 --- a/tools/NatsNet.PortTracker/Program.cs +++ b/tools/NatsNet.PortTracker/Program.cs @@ -41,6 +41,7 @@ rootCommand.Add(ReportCommands.Create(dbOption, schemaOption)); rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption)); rootCommand.Add(AuditCommand.Create(dbOption)); rootCommand.Add(OverrideCommands.Create(dbOption)); +rootCommand.Add(BatchCommands.Create(dbOption)); var parseResult = rootCommand.Parse(args); return await parseResult.InvokeAsync();