From c101cc87160d723a78441975cb8a8dbef37a27ea Mon Sep 17 00:00:00 2001 From: "Claude (Codeman maintainer)" Date: Sun, 14 Jun 2026 22:24:46 +0200 Subject: [PATCH] fix(doctor): correct Node minimum, drop phantom gemini, add pdftoppm, validate --category Review fixes on top of the `codeman doctor` checker: - Node minVersion 18.0.0 -> 22.0.0. package.json engines is ">=22.0.0" and the docs/CI require Node 22+, so doctor was green-lighting Node 18-21 (a false pass). - Remove the phantom `gemini` registry entry. Codeman has no Gemini backend (SessionMode = 'claude' | 'shell' | 'opencode' | 'codex'); the entry advertised a dependency that nothing uses. - Add `pdftoppm` (poppler) to the office group. document-thumbnailer.ts calls pdftoppm with no fallback as the sole PDF/Office first-page thumbnail renderer, yet it was absent from the registry, so doctor never reported it missing. - Fix the `--category` mismatch: the help advertised `documents|media` categories that the ToolCategory type/registry never defined, and an unknown category silently produced an empty "all healthy" table. Introduce TOOL_CATEGORIES as the single source of truth (type + help + validation); an invalid `--category` now errors with the valid list and exits 2. Verified: tsc, lint, format:check all clean; both dependency tests pass (20); `doctor` runs correctly (Node 22.22 ok, pdftoppm detected, no gemini), `--category media` errors with exit 2, `--category office` lists libreoffice/pdftoppm/msoffice. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli.ts | 9 +++++++-- src/config/dependency-registry.ts | 32 +++++++++++++++++++++---------- test/dependency-checker.test.ts | 4 ++-- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 6f249a3e..03883bc3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -631,11 +631,16 @@ program .alias('check-deps') .description('Check Codeman tool dependencies (Node, Claude CLI, tmux, LibreOffice, MS Office)') .option('--json', 'Output structured JSON instead of a table') - .option('--category ', 'Only check one category (core|office|documents|media|other)') + .option('--category ', 'Only check one category (core|office|other)') .action(async (options) => { const { createRealHost, checkAll } = await import('./utils/dependency-checker.js'); const { renderTable, renderJson, computeExitCode } = await import('./utils/dependency-report.js'); - const { DEPENDENCY_REGISTRY } = await import('./config/dependency-registry.js'); + const { DEPENDENCY_REGISTRY, TOOL_CATEGORIES } = await import('./config/dependency-registry.js'); + + if (options.category && !(TOOL_CATEGORIES as readonly string[]).includes(options.category)) { + console.error(`Unknown category "${options.category}". Valid categories: ${TOOL_CATEGORIES.join(', ')}`); + process.exit(2); + } const host = createRealHost(); const registry = options.category diff --git a/src/config/dependency-registry.ts b/src/config/dependency-registry.ts index 2e4a6220..bc04c485 100644 --- a/src/config/dependency-registry.ts +++ b/src/config/dependency-registry.ts @@ -8,7 +8,11 @@ */ export type ProbeEnvironment = 'linux' | 'darwin' | 'win32' | 'wsl'; -export type ToolCategory = 'core' | 'office' | 'other'; + +/** The valid `--category` filter values; single source of truth for the type, the CLI + * help text, and CLI input validation. */ +export const TOOL_CATEGORIES = ['core', 'office', 'other'] as const; +export type ToolCategory = (typeof TOOL_CATEGORIES)[number]; /** Resolve a binary on the PATH and read its version. */ export interface PathResolver { @@ -49,7 +53,7 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ label: 'Node.js', category: 'core', required: true, - minVersion: '18.0.0', + minVersion: '22.0.0', resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['node'], versionArg: '--version' } }], installHint: { linux: 'https://nodejs.org', darwin: 'brew install node', wsl: 'https://nodejs.org' }, }, @@ -86,14 +90,6 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ usedBy: ['Codex sessions'], resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['codex'], versionArg: '--version' } }], }, - { - id: 'gemini', - label: 'Gemini CLI', - category: 'core', - required: false, - usedBy: ['Gemini sessions'], - resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['gemini'], versionArg: '--version' } }], - }, { id: 'libreoffice', label: 'LibreOffice', @@ -108,6 +104,22 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ ], installHint: { linux: 'sudo apt install libreoffice', darwin: 'brew install --cask libreoffice' }, }, + { + id: 'pdftoppm', + label: 'pdftoppm', + category: 'office', + required: false, + usedBy: ['document preview', 'PDF/Office first-page thumbnails'], + // poppler's pdftoppm prints its version to stderr; presence is what matters here. + resolvers: [ + { match: ['linux', 'darwin', 'wsl'], resolver: { kind: 'path', bins: ['pdftoppm'], versionArg: '-v' } }, + ], + installHint: { + linux: 'sudo apt install poppler-utils', + darwin: 'brew install poppler', + wsl: 'sudo apt install poppler-utils', + }, + }, { id: 'msoffice', label: 'MS Office', diff --git a/test/dependency-checker.test.ts b/test/dependency-checker.test.ts index cc82966c..b3bb5573 100644 --- a/test/dependency-checker.test.ts +++ b/test/dependency-checker.test.ts @@ -22,8 +22,8 @@ describe('DEPENDENCY_REGISTRY', () => { .map((t) => t.id) .sort(); expect(required).toEqual(['node', 'tmux']); - // all four agent CLIs are optional (Codeman runs any of them) - const agentClis = ['claude', 'opencode', 'codex', 'gemini']; + // all agent CLIs are optional (Codeman runs any of them) + const agentClis = ['claude', 'opencode', 'codex']; expect(DEPENDENCY_REGISTRY.filter((t) => agentClis.includes(t.id)).every((t) => t.required === false)).toBe(true); const office = DEPENDENCY_REGISTRY.filter((t) => t.category === 'office'); expect(office.every((t) => t.required === false)).toBe(true);