fix(workspace): respect explicit workspaceMode:false, improve exclusion test

Address PR #1739 review feedback:

- P1 (greptile): When workspaceMode is explicitly false in config.json,
  skip the auto-detection fallback so toggling workspace mode off via the
  dashboard has a lasting effect (was being re-enabled on next registration).
- CodeRabbit: node_modules exclusion test now includes a real sibling
  sub-repo to prove the exclusion is the gate, not just absence of
  detection.
- Add test for workspaceMode:false config.json guard.
This commit is contained in:
gsxdsm
2026-06-24 08:16:36 -07:00
parent 9aaf911735
commit 42342eff03
2 changed files with 71 additions and 9 deletions

View File

@@ -154,8 +154,45 @@ describe("ensureGitRepositoryForProjectPath", () => {
await git(fakePkg, ["add", "index.js"]);
await git(fakePkg, ["commit", "-m", "init"]);
// Also create a real sibling sub-repo to prove it IS detected while node_modules is excluded
const realRepo = join(projectPath, "my-app");
mkdirSync(realRepo, { recursive: true });
await git(realRepo, ["init", "-b", "main"]);
await git(realRepo, ["config", "user.email", "test@test.com"]);
await git(realRepo, ["config", "user.name", "Test"]);
writeFileSync(join(realRepo, "README.md"), "# my-app\n");
await git(realRepo, ["add", "README.md"]);
await git(realRepo, ["commit", "-m", "init"]);
const detected = await detectWorkspaceRepos(projectPath);
expect(detected).toEqual([]);
// node_modules is excluded; my-app is detected
expect(detected).toEqual(["my-app"]);
});
it("skips auto-detection when workspaceMode is explicitly false in config.json", async () => {
const projectPath = tempDir("fusion-git-workspace-disabled-");
// Create a real git sub-repo so detectWorkspaceRepos would find it
const subRepo = join(projectPath, "repo-a");
mkdirSync(subRepo, { recursive: true });
await git(subRepo, ["init", "-b", "main"]);
await git(subRepo, ["config", "user.email", "test@test.com"]);
await git(subRepo, ["config", "user.name", "Test"]);
writeFileSync(join(subRepo, "README.md"), "# repo-a\n");
await git(subRepo, ["add", "README.md"]);
await git(subRepo, ["commit", "-m", "init"]);
// Write config.json with workspaceMode: false (user disabled it via dashboard)
mkdirSync(join(projectPath, ".fusion"), { recursive: true });
writeFileSync(
join(projectPath, ".fusion", "config.json"),
JSON.stringify({ settings: { workspaceMode: false } }),
);
const outcome = await ensureGitRepositoryForProjectPath(projectPath);
// Should proceed to git init, not workspace detection
expect(outcome).toBe("initialized");
expect(existsSync(join(projectPath, ".git"))).toBe(true);
});
});

View File

@@ -68,16 +68,24 @@ export async function ensureGitRepositoryForProjectPath(
CLI interactive setup writes workspace.json explicitly, but dashboard POST /api/projects
and `fn project add` do not — without this fallback they would create a stray .git at the
workspace root because loadWorkspaceConfig returned null.
FNXC:Workspace 2026-06-24-17:00:
If the user has explicitly disabled workspace mode (workspaceMode: false in config.json),
skip auto-detection and proceed to git init. Without this guard, toggling workspace mode off
via the dashboard would have no lasting effect — the fallback would re-detect sub-repos and
re-create workspace.json on the next registration call.
*/
const detectedRepos = await detectWorkspaceRepos(projectPath, runner, timeout);
if (detectedRepos.length > 0) {
try {
await saveWorkspaceConfig(projectPath, { repos: detectedRepos });
} catch {
// Best-effort: persist for the fast path on future calls, but don't fail
// the current registration if the write fails (permissions, disk full, etc.).
if (!(await isWorkspaceModeExplicitlyDisabled(projectPath))) {
const detectedRepos = await detectWorkspaceRepos(projectPath, runner, timeout);
if (detectedRepos.length > 0) {
try {
await saveWorkspaceConfig(projectPath, { repos: detectedRepos });
} catch {
// Best-effort: persist for the fast path on future calls, but don't fail
// the current registration if the write fails (permissions, disk full, etc.).
}
return "existing";
}
return "existing";
}
try {
@@ -195,6 +203,23 @@ export interface WorkspaceConfig {
const WORKSPACE_CONFIG_FILENAME = "workspace.json";
/**
* Reads .fusion/config.json and returns true when `workspaceMode` is explicitly
* set to `false`. This guards the auto-detection fallback so a user who has
* intentionally disabled workspace mode doesn't get it silently re-enabled.
*/
async function isWorkspaceModeExplicitlyDisabled(projectPath: string): Promise<boolean> {
try {
const { readFile } = await import("node:fs/promises");
const { join } = await import("node:path");
const raw = await readFile(join(projectPath, ".fusion", "config.json"), "utf-8");
const config = JSON.parse(raw) as { settings?: { workspaceMode?: boolean } };
return config.settings?.workspaceMode === false;
} catch {
return false;
}
}
/*
FNXC:Workspace 2026-06-22-00:00:
Workspace repo entries are later joined onto the workspace root to resolve worktrees, so an