Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/host-daemon/src/command-handlers/file-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,42 @@ describe("listPathsRecursively", () => {
}
});

it("includes project dot paths without traversing Git internals or dependencies", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "bb-file-list-"));
try {
await fs.mkdir(path.join(root, ".github", "workflows"), {
recursive: true,
});
await fs.writeFile(path.join(root, ".github", "workflows", "ci.yml"), "");
await fs.writeFile(path.join(root, ".env"), "");
await fs.mkdir(path.join(root, ".git"));
await fs.writeFile(path.join(root, ".git", "config"), "");
await fs.mkdir(path.join(root, "node_modules", "dependency"), {
recursive: true,
});
await fs.writeFile(
path.join(root, "node_modules", "dependency", "index.js"),
"",
);

const result = await listPathsRecursively({
dir: root,
root,
includeFiles: true,
includeDirectories: true,
});

expect(result.map((entry) => entry.path).sort()).toEqual([
".env",
".github",
".github/workflows",
".github/workflows/ci.yml",
]);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});

it("does not return symlinked files as regular path entries", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "bb-file-list-"));
try {
Expand Down
5 changes: 3 additions & 2 deletions apps/host-daemon/src/command-handlers/file-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export interface ListPathsRecursivelyArgs extends PathListInclusion {
root: string;
}

const RECURSIVE_PATH_SKIP_NAMES = new Set([".git", "node_modules"]);

function shouldIncludePath(
pathKind: HostPathEntryKind,
inclusion: PathListInclusion,
Expand Down Expand Up @@ -140,8 +142,7 @@ export async function listPathsRecursively(
const entries = await fs.readdir(args.dir, { withFileTypes: true });
const results: ListedPath[] = [];
for (const entry of entries) {
if (entry.name.startsWith(".")) continue;
if (entry.name === "node_modules") continue;
if (RECURSIVE_PATH_SKIP_NAMES.has(entry.name)) continue;
if (entry.isSymbolicLink()) continue;

const fullPath = path.join(args.dir, entry.name);
Expand Down
6 changes: 5 additions & 1 deletion packages/host-daemon-contract/src/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Version 147 includes ordinary project dot paths in recursive file listings
// while continuing to exclude `.git`, `node_modules`, and symlinks. Older
// daemons silently omit paths such as `.github/workflows/ci.yml`.
//
// Version 146 adds the lightweight `host.list_branch_options` RPC so branch
// pickers can read cached refs while the daemon refreshes remotes in the
// background. Older daemons cannot parse or serve that command.
Expand Down Expand Up @@ -110,7 +114,7 @@
//
// The version mismatch is what triggers the enrolled daemon's automatic update
// instead of an `invalid-message` reconnect loop.
export const HOST_DAEMON_PROTOCOL_VERSION = 146 as const;
export const HOST_DAEMON_PROTOCOL_VERSION = 147 as const;

/**
* Absolute ceiling for any executable artifact delivered to a host daemon —
Expand Down
2 changes: 1 addition & 1 deletion packages/host-daemon-contract/test/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,7 @@ describe("host-daemon command schemas", () => {
// mixed version. Version 113 carried the Devin Desktop open target rename
// and remains part of the protocol lineage.
it("uses the current host-daemon protocol version", () => {
expect(HOST_DAEMON_PROTOCOL_VERSION).toBe(146);
expect(HOST_DAEMON_PROTOCOL_VERSION).toBe(147);
expect(HOST_ARTIFACT_MAX_BYTES).toBe(256 * 1024 * 1024);
});

Expand Down