agent: append .js to --save destinations like /save does

The one-shot --save flag bypassed save.parseCommand, so '--save test'
wrote a file literally named 'test' while the REPL's /save appended the
extension. Share one ensureJsExtension helper so the paths can't drift.
This commit is contained in:
Adrià Arrufat
2026-07-10 11:34:53 +02:00
parent 9500c6a653
commit 0f686381dd
2 changed files with 22 additions and 5 deletions

View File

@@ -1165,7 +1165,8 @@ fn synthesizeSave(self: *Agent, arena: std.mem.Allocator, filename: ?[]const u8,
fn saveOneShot(self: *Agent) void {
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
self.synthesizeSaveTo(arena.allocator(), self.one_shot_save.?, .replace, self.one_shot_task.?);
const path = save.ensureJsExtension(arena.allocator(), self.one_shot_save.?) catch self.one_shot_save.?;
self.synthesizeSaveTo(arena.allocator(), path, .replace, self.one_shot_task.?);
}
/// LLM synthesis + write for an already-resolved destination. Shared by the

View File

@@ -53,11 +53,19 @@ pub fn parseCommand(arena: std.mem.Allocator, rest: []const u8) !Command {
after = trimmed[tok_end..];
}
if (name.len == 0) return error.EmptyFilename;
if (!std.mem.endsWith(u8, name, ".js")) {
name = try std.mem.concat(arena, u8, &.{ name, ".js" });
}
const prompt = std.mem.trim(u8, after, &std.ascii.whitespace);
return .{ .filename = name, .prompt = if (prompt.len == 0) null else prompt };
return .{
.filename = try ensureJsExtension(arena, name),
.prompt = if (prompt.len == 0) null else prompt,
};
}
/// `name` with `.js` appended when missing; may alias `name` or be
/// arena-allocated. Shared by `/save` parsing and the one-shot `--save` flag
/// so the two paths can't drift.
pub fn ensureJsExtension(arena: std.mem.Allocator, name: []const u8) ![]const u8 {
if (std.mem.endsWith(u8, name, ".js")) return name;
return std.mem.concat(arena, u8, &.{ name, ".js" });
}
pub fn randomFilename(arena: std.mem.Allocator) ![]const u8 {
@@ -119,6 +127,14 @@ test "parseCommand: filename only" {
try std.testing.expect(r.prompt == null);
}
test "ensureJsExtension appends only when missing" {
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
try std.testing.expectEqualStrings("out.js", try ensureJsExtension(arena.allocator(), "out"));
try std.testing.expectEqualStrings("out.js", try ensureJsExtension(arena.allocator(), "out.js"));
try std.testing.expectEqualStrings("a/b.thing.js", try ensureJsExtension(arena.allocator(), "a/b.thing"));
}
test "parseCommand: filename and prompt" {
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();