feat: list_dir — live BFS listing (gitignore, 10k cap) - #697
Conversation
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
Fixes #696. ls/tree stay index queries. list_dir walks the live tree.
|
👋 Thanks for the contribution! Quick heads-up: this repo lands changes on the current Please retarget this PR via Edit → base branch to the active (Automated hint — reply here if you need a hand.) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04215a30da
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (names.items) |e| { | ||
| if (isGitComponent(e.name)) continue; | ||
| const child_rel = if (rel.len == 0) e.name else try join(w.arena, rel, e.name); | ||
| const child_abs = try join(w.arena, w.root_abs, child_rel); | ||
| if (try skip(w, child_abs, e.is_dir)) continue; |
There was a problem hiding this comment.
Filter sensitive paths before adding listing nodes
When a secret is not explicitly covered by .gitignore, this loop adds names such as .env, credentials.json, private keys, and files below .ssh to the live listing. The existing index and read paths deliberately apply watcher.isSensitivePath, so list_dir bypasses the repository's shared sensitive-path policy and exposes paths that other discovery operations hide; apply that filter before appending either files or directories.
AGENTS.md reference: AGENTS.md:L16-L17
Useful? React with 👍 / 👎.
| if (pat[0] == '*') { | ||
| var i: usize = 0; | ||
| while (i <= s.len) : (i += 1) { | ||
| if (globSeg(pat[1..], s[i..])) return true; | ||
| } |
There was a problem hiding this comment.
Bound recursive gitignore wildcard matching
On a repository containing an adversarial rule such as repeated *a groups followed by a nonmatching suffix, each * recursively retries every remaining filename position, producing combinatorial work before a single entry can be processed. Because .gitignore is untrusted repository input and the 20,000-item cap is reached only after matching, one crafted pattern can effectively hang list_dir; memoize (pattern_offset, string_offset) states or use an iterative bounded matcher.
AGENTS.md reference: AGENTS.md:L16-L16
Useful? React with 👍 / 👎.
| fn underRoot(abs: []const u8, root: []const u8) bool { | ||
| if (!std.mem.startsWith(u8, abs, root)) return false; | ||
| return abs.len == root.len or abs[root.len] == '/'; | ||
| } |
There was a problem hiding this comment.
Accept native separators in root containment checks
For a nested listing on Windows, realPath returns a native \ boundary after project_abs, so this comparison to '/' rejects every valid subdirectory as error.Escape. The same check also rejects children when the POSIX project root is / because /tmp has t, not another slash, at root.len; use a platform-aware path containment check and special-case filesystem roots.
Useful? React with 👍 / 👎.
| if (git_root) |g| { | ||
| var buf: [std.fs.max_path_bytes]u8 = undefined; | ||
| const ex = std.fmt.bufPrint(&buf, "{s}/.git/info/exclude", .{g}) catch return out.items; | ||
| loadFile(io, arena, ex, g, &out); |
There was a problem hiding this comment.
Load info excludes before repository gitignore rules
When .git/info/exclude conflicts with a .gitignore already loaded by loadClimb, appending the info rules here makes them win because verdict uses the last matching rule. Git gives per-directory .gitignore files higher precedence—for example, root .gitignore containing !foo must re-include a path excluded as foo in .git/info/exclude—so the current listing hides files that Git reports as unignored.
Useful? React with 👍 / 👎.
Fixes #696.
ls/treestay index queries.list_dirwalks the live tree (gitignore, 10k-char cap, collapsed subtree summaries).This matches graff's in-process codedb
list_dir(codegraff PR #579).Cold CLI exits before snapshot/daemon (same early-exit shape as
status). Temp-root guard is skipped because this is a live walk, not an index.CLI:
codedb list_dir [path]— no index required. Verified on an unindexed/tmptree.MCP: handler is in
src/mcp_list_dir.zig.src/mcp.zigis 317KB so it is not rewritten in this PR (Contents API needs the whole file). Applypatches/696-mcp-list-dir.patchon this branch to registercodedb_list_dir(Tool enum + tools_list + dispatch). Until that lands, MCP clients keep usingcodedb_ls(index) or the CLI.