diff --git a/src/cli/doctor/checks/index.ts b/src/cli/doctor/checks/index.ts index 0ad6821fd..55e908b32 100644 --- a/src/cli/doctor/checks/index.ts +++ b/src/cli/doctor/checks/index.ts @@ -4,6 +4,7 @@ import { checkSystem, gatherSystemInfo } from "./system" import { checkConfig } from "./config" import { checkTools, gatherToolsSummary } from "./tools" import { checkModels } from "./model-resolution" +import { checkTeamMode } from "./team-mode" export type { CheckDefinition } export * from "./model-resolution-types" @@ -32,5 +33,10 @@ export function getAllCheckDefinitions(): CheckDefinition[] { name: CHECK_NAMES[CHECK_IDS.MODELS], check: checkModels, }, + { + id: CHECK_IDS.TEAM_MODE, + name: CHECK_NAMES[CHECK_IDS.TEAM_MODE], + check: checkTeamMode, + }, ] } diff --git a/src/cli/doctor/checks/team-mode.ts b/src/cli/doctor/checks/team-mode.ts new file mode 100644 index 000000000..3da6e15be --- /dev/null +++ b/src/cli/doctor/checks/team-mode.ts @@ -0,0 +1,63 @@ +import { checkTeamModeDependencies } from "../../../features/team-mode/deps" +import { resolveBaseDir } from "../../../features/team-mode/team-registry/paths" +import { TeamModeConfigSchema } from "../../../config/schema/team-mode" +import { CHECK_IDS, CHECK_NAMES } from "../constants" +import type { CheckResult } from "../types" +import { readFileSync, promises as fs } from "node:fs" +import path from "node:path" +import { detectPluginConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared" + +export async function checkTeamMode(): Promise { + const config = loadTeamModeConfig() + const teamModeConfig = TeamModeConfigSchema.parse(config.team_mode ?? {}) + if (!teamModeConfig.enabled) { + return { name: CHECK_NAMES[CHECK_IDS.TEAM_MODE], status: "skip", message: "team_mode: disabled", issues: [] } + } + + const deps = await checkTeamModeDependencies(teamModeConfig) + const baseDir = resolveBaseDir(teamModeConfig) + const [baseDirExists, teamCount, runtimeCount] = await Promise.all([ + pathExists(baseDir), + safeCount(path.join(baseDir, "teams")), + safeCount(path.join(baseDir, "runtime")), + ]) + const baseDirMessage = baseDirExists ? `base dir: ok` : `base dir: missing (plugin init will create it on first use)` + + return { + name: CHECK_NAMES[CHECK_IDS.TEAM_MODE], + status: deps.tmuxAvailable && deps.gitAvailable ? "pass" : "warn", + message: `team_mode: enabled | tmux: ${deps.tmuxAvailable ? "ok" : "missing"} | git: ${deps.gitAvailable ? "ok" : "missing"} | ${baseDirMessage} | declared: ${teamCount} | runtime dirs: ${runtimeCount}`, + details: undefined, + issues: [], + } +} + +function loadTeamModeConfig() { + const projectConfig = detectPluginConfigFile(path.join(process.cwd(), ".opencode")) + const userConfig = detectPluginConfigFile(getOpenCodeConfigDir({ binary: "opencode" })) + const configPath = projectConfig.format !== "none" ? projectConfig.path : userConfig.path + if (!configPath) return { team_mode: undefined } + try { + return parseJsonc<{ team_mode?: { enabled?: boolean } }>(readFileSync(configPath, "utf-8")) + } catch { + return { team_mode: undefined } + } +} + +async function safeCount(dir: string): Promise { + try { + const entries = await fs.readdir(dir, { withFileTypes: true }) + return entries.filter((entry) => entry.isDirectory()).length + } catch { + return 0 + } +} + +async function pathExists(dir: string): Promise { + try { + const stats = await fs.stat(dir) + return stats.isDirectory() + } catch { + return false + } +} diff --git a/src/cli/doctor/constants.ts b/src/cli/doctor/constants.ts index ea2c43a98..dad93f8e8 100644 --- a/src/cli/doctor/constants.ts +++ b/src/cli/doctor/constants.ts @@ -23,6 +23,7 @@ export const CHECK_IDS = { CONFIG: "config", TOOLS: "tools", MODELS: "models", + TEAM_MODE: "team-mode", } as const export const CHECK_NAMES: Record = { @@ -30,6 +31,7 @@ export const CHECK_NAMES: Record = { [CHECK_IDS.CONFIG]: "Configuration", [CHECK_IDS.TOOLS]: "Tools", [CHECK_IDS.MODELS]: "Models", + [CHECK_IDS.TEAM_MODE]: "Team Mode", } as const export const EXIT_CODES = {