(projectRoot)
| 10 | const VALID_RUNTIMES = listRuntimeIds(); |
| 11 | |
| 12 | function detectRuntime(projectRoot) { |
| 13 | const root = projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd(); |
| 14 | |
| 15 | const envRuntime = process.env.CITADEL_RUNTIME; |
| 16 | if (envRuntime && VALID_RUNTIMES.includes(envRuntime)) { |
| 17 | return { runtime: envRuntime, method: 'env' }; |
| 18 | } |
| 19 | |
| 20 | try { |
| 21 | const isWin = process.platform === 'win32'; |
| 22 | let parentInfo = ''; |
| 23 | if (isWin) { |
| 24 | try { |
| 25 | parentInfo = execSync( |
| 26 | `wmic process where "ProcessId=${process.ppid}" get CommandLine /format:list`, |
| 27 | { encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] } |
| 28 | ).toLowerCase(); |
| 29 | } catch { |
| 30 | parentInfo = execSync( |
| 31 | `tasklist /FI "PID eq ${process.ppid}" /FO CSV /NH`, |
| 32 | { encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] } |
| 33 | ).toLowerCase(); |
| 34 | } |
| 35 | } else { |
| 36 | parentInfo = execSync( |
| 37 | `ps -p ${process.ppid} -o command= 2>/dev/null || true`, |
| 38 | { encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] } |
| 39 | ).toLowerCase(); |
| 40 | } |
| 41 | |
| 42 | if (parentInfo.includes('codex')) { |
| 43 | return { runtime: 'codex', method: 'process-tree' }; |
| 44 | } |
| 45 | if (parentInfo.includes('claude')) { |
| 46 | return { runtime: 'claude-code', method: 'process-tree' }; |
| 47 | } |
| 48 | } catch { |
| 49 | // Ignore and continue to directory markers. |
| 50 | } |
| 51 | |
| 52 | const hasClaudeDir = fs.existsSync(path.join(root, '.claude')); |
| 53 | const hasCodexDir = fs.existsSync(path.join(root, '.codex')); |
| 54 | |
| 55 | if (hasCodexDir && !hasClaudeDir) { |
| 56 | return { runtime: 'codex', method: 'directory-marker' }; |
| 57 | } |
| 58 | if (hasClaudeDir && !hasCodexDir) { |
| 59 | return { runtime: 'claude-code', method: 'directory-marker' }; |
| 60 | } |
| 61 | if (hasClaudeDir && hasCodexDir) { |
| 62 | try { |
| 63 | const claudeStat = fs.statSync(path.join(root, '.claude')); |
| 64 | const codexStat = fs.statSync(path.join(root, '.codex')); |
| 65 | const runtime = codexStat.mtimeMs > claudeStat.mtimeMs ? 'codex' : 'claude-code'; |
| 66 | return { runtime, method: 'directory-marker-recency' }; |
| 67 | } catch { |
| 68 | return { runtime: 'claude-code', method: 'directory-marker-fallback' }; |
| 69 | } |
no outgoing calls
no test coverage detected