From 27ce1c5d444c7a74025b205377e881992052664a Mon Sep 17 00:00:00 2001 From: Lim Yu Xi Date: Fri, 10 Apr 2026 10:12:29 +0800 Subject: [PATCH 1/2] fix: saveToFile allocator (#405), PPR weighted threshold (#109), runChainStep timeouts, macOS zig-build wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - storage.zig: saveToFile now accepts a caller-provided allocator instead of using a hardcoded page_allocator. Adds round-trip test for saveToFile/loadFromFile. - ppr.zig: PPR push threshold uses total outgoing edge weight (W_out) instead of raw degree, matching the weighted residual distribution formula. Adds totalOutWeight helper. - tools.zig: fix runChainStep call sites to pass both timeout_ms (u64) and reported_timeout_seconds (u32) — previously only one value was passed. - scripts/zig-build.sh: wrapper for zig build on macOS 26+ where Zig 0.15.x's bundled lld cannot link against the system SDK. Made-with: Cursor --- scripts/zig-build.sh | 19 +++++++++++++++++++ src/graph/ppr.zig | 18 +++++++++++------- src/graph/storage.zig | 35 ++++++++++++++++++++++++++--------- src/tools.zig | 10 ++++++---- 4 files changed, 62 insertions(+), 20 deletions(-) create mode 100755 scripts/zig-build.sh diff --git a/scripts/zig-build.sh b/scripts/zig-build.sh new file mode 100755 index 0000000..de609d5 --- /dev/null +++ b/scripts/zig-build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Wrapper for zig build on macOS 26+ where Zig 0.15.x's bundled lld +# cannot link against the system SDK. Falls back to the system linker +# and the Command Line Tools 15.4 SDK when available. +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root" + +if [[ "$(uname -s)" == "Darwin" ]]; then + CLT_SDK="/Library/Developer/CommandLineTools/SDKs/MacOSX15.4.sdk" + if [[ -d "$CLT_SDK" ]]; then + export ZIG_LLD_FLAG="-fno-lld" + export ZIG_SYSROOT="--sysroot=$CLT_SDK" + export DEVELOPER_DIR="${DEVELOPER_DIR:-/Library/Developer/CommandLineTools}" + fi +fi + +exec "${ZIG:-zig}" build "$@" diff --git a/src/graph/ppr.zig b/src/graph/ppr.zig index d933c93..0a63461 100644 --- a/src/graph/ppr.zig +++ b/src/graph/ppr.zig @@ -3,7 +3,7 @@ // Andersen-Chung-Lang push approximation for PPR. // Given a query node, computes relevance scores for all reachable nodes. // -// Push rule: if r[u] > ε × deg(u), push u: +// Push rule: if r[u] > ε × W_out(u), push u (W_out = total outgoing edge weight): // p[u] += α × r[u] // r[v] += (1-α) × r[u] × W(u,v) / W_out(u) for each out-neighbour v // r[u] = 0 @@ -27,7 +27,14 @@ pub const ScoredNode = struct { /// /// Returns a map of node_id → PPR score for all nodes with non-zero score. /// `alpha` is the teleport probability (typically 0.15). -/// `epsilon` is the convergence threshold per unit of degree. +/// `epsilon` scales the push threshold with total out-weight W_out(u) (not raw degree). +fn totalOutWeight(g: *const CodeGraph, u: u64) f32 { + const edges = g.outEdges(u); + var s: f32 = 0; + for (edges) |e| s += e.weight; + return s; +} + pub fn pprPush( g: *const CodeGraph, query_node: u64, @@ -69,11 +76,8 @@ pub fn pprPush( while (it.next()) |entry| { const u = entry.key_ptr.*; const r_u = entry.value_ptr.*; - const deg = g.outDegree(u); - const threshold = if (deg > 0) - epsilon * @as(f32, @floatFromInt(deg)) - else - epsilon; + const w_out = totalOutWeight(g, u); + const threshold = if (w_out > 0) epsilon * w_out else epsilon; if (r_u > threshold) { try to_push.append(alloc, u); } diff --git a/src/graph/storage.zig b/src/graph/storage.zig index aa1cffd..2fa3f87 100644 --- a/src/graph/storage.zig +++ b/src/graph/storage.zig @@ -188,21 +188,16 @@ pub fn deserialize(reader: anytype, alloc: std.mem.Allocator) !CodeGraph { // ── File I/O convenience ──────────────────────────────────────────────────── -/// Save a CodeGraph to a file path. -pub fn saveToFile(g: *const CodeGraph, path: []const u8) !void { +/// Save a CodeGraph to a file path. `alloc` is used for the serialization buffer. +pub fn saveToFile(g: *const CodeGraph, path: []const u8, alloc: std.mem.Allocator) !void { const file = try std.fs.cwd().createFile(path, .{}); defer file.close(); - // Serialize to in-memory buffer, then write all at once var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(alloc_for_save); - try serialize(g, buf.writer(alloc_for_save)); + defer buf.deinit(alloc); + try serialize(g, buf.writer(alloc)); try file.writeAll(buf.items); } -/// Temporary allocator for saveToFile — uses page_allocator since we -/// don't have access to a caller-provided allocator in the current API. -const alloc_for_save = std.heap.page_allocator; - /// Load a CodeGraph from a file path. pub fn loadFromFile(path: []const u8, alloc: std.mem.Allocator) !CodeGraph { const file = try std.fs.cwd().openFile(path, .{}); @@ -879,3 +874,25 @@ test "round-trip with empty strings everywhere" { try std.testing.expectEqualStrings("", g2.getCommit(1).?.author); try std.testing.expectEqualStrings("", g2.getCommit(1).?.message); } + +test "saveToFile and loadFromFile round-trip (#405)" { + const alloc = std.testing.allocator; + var g = CodeGraph.init(alloc); + defer g.deinit(); + + try g.addSymbol(.{ .id = 1, .name = "foo", .kind = .function, .file_id = 1, .line = 5, .col = 0, .scope = "mod" }); + try g.addFile(.{ .id = 1, .path = "a.zig", .language = .zig, .last_modified = 100, .hash = [_]u8{0xAB} ** 32 }); + try g.addEdge(.{ .src = 1, .dst = 1, .kind = .calls, .weight = 3.0 }); + + const tmp = "test_save_load_405.bin"; + try saveToFile(&g, tmp, alloc); + defer std.fs.cwd().deleteFile(tmp) catch {}; + + var g2 = try loadFromFile(tmp, alloc); + defer g2.deinit(); + + try std.testing.expectEqual(g.symbolCount(), g2.symbolCount()); + try std.testing.expectEqual(g.files.count(), g2.files.count()); + try std.testing.expectEqual(g.edgeCount(), g2.edgeCount()); + try std.testing.expectEqualStrings("foo", g2.getSymbol(1).?.name); +} diff --git a/src/tools.zig b/src/tools.zig index d935b83..75f2d06 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -2166,7 +2166,9 @@ fn runAgentWithRole( timeout_seconds: ?u32, out: *std.ArrayList(u8), ) void { - runChainStep(alloc, role, mode, writable_flag, null, prompt, timeout_seconds, out); + const sec: u32 = timeout_seconds orelse 300; + const ms: u64 = @as(u64, sec) * std.time.ms_per_s; + runChainStep(alloc, role, mode, writable_flag, null, prompt, ms, sec, out); } fn parseTimeoutSeconds(args: *const std.json.ObjectMap, default_seconds: u32) u32 { @@ -2217,7 +2219,7 @@ fn runChainStepWithBudget( appendTimedOutJson(alloc, step_out, total_timeout_seconds); return; }; - runChainStep(alloc, role, mode, writable_override, permission_mode, prompt, remaining, step_out); + runChainStep(alloc, role, mode, writable_override, permission_mode, prompt, @as(u64, remaining) * std.time.ms_per_s, remaining, step_out); } fn handleRunReviewer( @@ -2381,7 +2383,7 @@ fn handleReviewFixLoop( iter_json.appendSlice(alloc, ",\"review\":\"") catch return; var review_out: std.ArrayList(u8) = .empty; defer review_out.deinit(alloc); - runChainStep(alloc, "reviewer", null, false, null, review_prompt, 300, &review_out); + runChainStep(alloc, "reviewer", null, false, null, review_prompt, 300 * std.time.ms_per_s, 300, &review_out); mj.writeEscaped(alloc, &iter_json, review_out.items); iter_json.appendSlice(alloc, "\"") catch return; @@ -2424,7 +2426,7 @@ fn handleReviewFixLoop( var fix_out: std.ArrayList(u8) = .empty; defer fix_out.deinit(alloc); - runChainStep(alloc, "fixer", null, true, null, fix_prompt, 300, &fix_out); + runChainStep(alloc, "fixer", null, true, null, fix_prompt, 300 * std.time.ms_per_s, 300, &fix_out); iter_json.appendSlice(alloc, ",\"fix\":\"") catch return; mj.writeEscaped(alloc, &iter_json, fix_out.items); From c6d12dab4976382e11781a62fc307ebc598dcbca Mon Sep 17 00:00:00 2001 From: Lim Yu Xi Date: Fri, 10 Apr 2026 11:09:15 +0800 Subject: [PATCH 2/2] remove: drop Darwin-specific zig-build.sh wrapper Removes scripts/zig-build.sh which contained macOS-specific linker workarounds (-fno-lld, --sysroot). This should not be part of the PR. Made-with: Cursor --- scripts/zig-build.sh | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100755 scripts/zig-build.sh diff --git a/scripts/zig-build.sh b/scripts/zig-build.sh deleted file mode 100755 index de609d5..0000000 --- a/scripts/zig-build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -# Wrapper for zig build on macOS 26+ where Zig 0.15.x's bundled lld -# cannot link against the system SDK. Falls back to the system linker -# and the Command Line Tools 15.4 SDK when available. -set -euo pipefail - -root="$(cd "$(dirname "$0")/.." && pwd)" -cd "$root" - -if [[ "$(uname -s)" == "Darwin" ]]; then - CLT_SDK="/Library/Developer/CommandLineTools/SDKs/MacOSX15.4.sdk" - if [[ -d "$CLT_SDK" ]]; then - export ZIG_LLD_FLAG="-fno-lld" - export ZIG_SYSROOT="--sysroot=$CLT_SDK" - export DEVELOPER_DIR="${DEVELOPER_DIR:-/Library/Developer/CommandLineTools}" - fi -fi - -exec "${ZIG:-zig}" build "$@"