diff --git a/cli/src/utils/suggest.zig b/cli/src/utils/suggest.zig deleted file mode 100644 index f85e167..0000000 --- a/cli/src/utils/suggest.zig +++ /dev/null @@ -1,264 +0,0 @@ -const std = @import("std"); - -/// Calculate Levenshtein distance between two strings -pub fn levenshteinDistance(allocator: std.mem.Allocator, s1: []const u8, s2: []const u8) !usize { - const m = s1.len + 1; - const n = s2.len + 1; - - // Create a 2D array for dynamic programming - var dp = try allocator.alloc(usize, m * n); - defer allocator.free(dp); - - // Initialize first row and column - for (0..m) |i| { - dp[i * n] = i; - } - for (0..n) |j| { - dp[j] = j; - } - - // Fill the matrix - for (1..m) |i| { - for (1..n) |j| { - const cost: usize = if (s1[i - 1] == s2[j - 1]) 0 else 1; - const deletion = dp[(i - 1) * n + j] + 1; - const insertion = dp[i * n + (j - 1)] + 1; - const substitution = dp[(i - 1) * n + (j - 1)] + cost; - dp[i * n + j] = @min(@min(deletion, insertion), substitution); - } - } - - return dp[(m - 1) * n + (n - 1)]; -} - -/// Find suggestions for a typo from a list of candidates -pub fn findSuggestions( - allocator: std.mem.Allocator, - input: []const u8, - candidates: []const []const u8, - max_distance: usize, - max_suggestions: usize, -) ![][]const u8 { - var suggestions = std.ArrayList([]const u8).empty; - defer suggestions.deinit(allocator); - - var distances = std.ArrayList(usize).empty; - defer distances.deinit(allocator); - - for (candidates) |candidate| { - const dist = try levenshteinDistance(allocator, input, candidate); - if (dist <= max_distance) { - try suggestions.append(allocator, candidate); - try distances.append(allocator, dist); - } - } - - // Sort by distance (bubble sort for simplicity with small lists) - const n = distances.items.len; - for (0..n) |i| { - for (0..n - i - 1) |j| { - if (distances.items[j] > distances.items[j + 1]) { - // Swap distances - const temp_dist = distances.items[j]; - distances.items[j] = distances.items[j + 1]; - distances.items[j + 1] = temp_dist; - // Swap corresponding suggestions - const temp_sugg = suggestions.items[j]; - suggestions.items[j] = suggestions.items[j + 1]; - suggestions.items[j + 1] = temp_sugg; - } - } - } - - // Return top suggestions - const count = @min(suggestions.items.len, max_suggestions); - const result = try allocator.alloc([]const u8, count); - for (0..count) |i| { - result[i] = try allocator.dupe(u8, suggestions.items[i]); - } - - return result; -} - -/// Suggest commands based on prefix matching -pub fn suggestCommands(input: []const u8) ?[]const []const u8 { - const all_commands = [_][]const u8{ - "init", "sync", "queue", "requeue", "status", - "monitor", "cancel", "prune", "watch", "dataset", - "experiment", "narrative", "outcome", "info", "logs", - "annotate", "validate", "compare", "find", "export", - }; - - // Exact match - no suggestion needed - for (all_commands) |cmd| { - if (std.mem.eql(u8, input, cmd)) return null; - } - - // Find prefix matches - var matches: [5][]const u8 = undefined; - var match_count: usize = 0; - - for (all_commands) |cmd| { - if (std.mem.startsWith(u8, cmd, input)) { - matches[match_count] = cmd; - match_count += 1; - if (match_count >= 5) break; - } - } - - if (match_count == 0) return null; - - // Return static slice - caller must not free - return matches[0..match_count]; -} - -/// Suggest flags for a command -pub fn suggestFlags(command: []const u8, input: []const u8) ?[]const []const u8 { - // Common flags for all commands - const common_flags = [_][]const u8{ "--help", "--verbose", "--quiet", "--json" }; - - // Command-specific flags - const queue_flags = [_][]const u8{ - "--commit", "--priority", "--cpu", "--memory", "--gpu", - "--gpu-memory", "--hypothesis", "--context", "--intent", "--expected-outcome", - "--experiment-group", "--tags", "--dry-run", "--validate", "--explain", - "--force", - }; - - const find_flags = [_][]const u8{ - "--tag", "--outcome", "--dataset", "--experiment-group", - "--author", "--after", "--before", "--limit", - }; - - const compare_flags = [_][]const u8{ - "--json", "--all", "--fields", - }; - - const export_flags = [_][]const u8{ - "--bundle", "--anonymize", "--anonymize-level", "--base", - }; - - // Select flags based on command - const flags: []const []const u8 = switch (std.meta.stringToEnum(Command, command) orelse .unknown) { - .queue => &queue_flags, - .find => &find_flags, - .compare => &compare_flags, - .export_cmd => &export_flags, - else => &common_flags, - }; - - // Find prefix matches - var matches: [5][]const u8 = undefined; - var match_count: usize = 0; - - // Check common flags first - for (common_flags) |flag| { - if (std.mem.startsWith(u8, flag, input)) { - matches[match_count] = flag; - match_count += 1; - if (match_count >= 5) break; - } - } - - // Then check command-specific flags - if (match_count < 5) { - for (flags) |flag| { - if (std.mem.startsWith(u8, flag, input)) { - // Avoid duplicates - var already_added = false; - for (0..match_count) |i| { - if (std.mem.eql(u8, matches[i], flag)) { - already_added = true; - break; - } - } - if (!already_added) { - matches[match_count] = flag; - match_count += 1; - if (match_count >= 5) break; - } - } - } - } - - if (match_count == 0) return null; - return matches[0..match_count]; -} - -const Command = enum { - init, - sync, - queue, - requeue, - status, - monitor, - cancel, - prune, - watch, - dataset, - experiment, - narrative, - outcome, - info, - logs, - annotate, - validate, - compare, - find, - export_cmd, - unknown, -}; - -/// Format suggestions into a helpful message -pub fn formatSuggestionMessage( - allocator: std.mem.Allocator, - input: []const u8, - suggestions: []const []const u8, -) ![]u8 { - if (suggestions.len == 0) return allocator.dupe(u8, ""); - - var buf = std.ArrayList(u8).empty; - defer buf.deinit(allocator); - - const writer = buf.writer(allocator); - try writer.print("Did you mean for '{s}': ", .{input}); - - for (suggestions, 0..) |sugg, i| { - if (i > 0) { - if (i == suggestions.len - 1) { - try writer.writeAll(" or "); - } else { - try writer.writeAll(", "); - } - } - try writer.print("'{s}'", .{sugg}); - } - - try writer.writeAll("?\n"); - - return buf.toOwnedSlice(allocator); -} - -/// Test the suggestion system -pub fn testSuggestions() !void { - const allocator = std.testing.allocator; - - // Test Levenshtein distance - const dist1 = try levenshteinDistance(allocator, "queue", "quee"); - std.debug.assert(dist1 == 1); - - const dist2 = try levenshteinDistance(allocator, "status", "statis"); - std.debug.assert(dist2 == 1); - - // Test suggestions - const candidates = [_][]const u8{ "queue", "query", "quiet", "quit" }; - const suggestions = try findSuggestions(allocator, "quee", &candidates, 2, 3); - defer { - for (suggestions) |s| allocator.free(s); - allocator.free(suggestions); - } - std.debug.assert(suggestions.len > 0); - std.debug.assert(std.mem.eql(u8, suggestions[0], "queue")); - - std.debug.print("Suggestion tests passed!\n", .{}); -}