/** * Statusline script section: version/colour setup, safe readers, and the * git/model/learning/security/swarm data collectors. * Spliced verbatim into generateStatuslineScript() output. */ export declare const STATUSLINE_DATA_SECTION = "// Read monomind version \u2014 check global install first, then CWD package.json\nfunction getVersion() {\n // 1. Monomind global install: script lives at /packages/@monomind/cli/dist/src/init/\n // or user project: .claude/helpers/statusline.cjs\n // Walk up to find a monomind package.json (has \"name\":\"monomind\" or \"@monomind/cli\")\n const scriptDir = path.dirname(__filename);\n const walkCandidates = [\n path.join(scriptDir, '..', '..', 'package.json'), // dist/src -> @monomind/cli\n path.join(scriptDir, '..', '..', '..', 'package.json'), // -> monomind umbrella\n path.join(scriptDir, '..', '..', '..', '..', 'package.json'),\n ];\n for (const p of walkCandidates) {\n try {\n const pkgStat = safeStat(p);\n if (!pkgStat || pkgStat.size > 1024 * 1024) continue;\n const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));\n if (pkg.version && (pkg.name === 'monomind' || pkg.name === '@monomind/cli' || (pkg.name || '').startsWith('@monomind'))) {\n return `v${pkg.version}`;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n }\n // 2. Fallback: npm global prefix (lib/node_modules on POSIX, node_modules on Windows)\n try {\n const { execSync } = require('child_process');\n const prefix = execSync('npm config get prefix', { encoding: 'utf-8', timeout: 2000 }).trim();\n const candidates = [\n path.join(prefix, 'lib', 'node_modules', 'monomind', 'package.json'),\n path.join(prefix, 'node_modules', 'monomind', 'package.json'),\n ];\n for (const globalPkgPath of candidates) {\n const globalPkgStat = safeStat(globalPkgPath);\n if (!globalPkgStat || globalPkgStat.size > 1024 * 1024) continue;\n const pkg = JSON.parse(fs.readFileSync(globalPkgPath, 'utf-8'));\n if (pkg.version) return `v${pkg.version}`;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n return 'v?';\n}\nconst VERSION = cached('version', TTL.slow, getVersion);\n\n// ANSI colors\nconst c = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[0;31m',\n green: '\\x1b[0;32m',\n yellow: '\\x1b[0;33m',\n blue: '\\x1b[0;34m',\n purple: '\\x1b[0;35m',\n cyan: '\\x1b[0;36m',\n brightRed: '\\x1b[1;31m',\n brightGreen: '\\x1b[1;32m',\n brightYellow: '\\x1b[1;33m',\n brightBlue: '\\x1b[1;34m',\n brightPurple: '\\x1b[1;35m',\n brightCyan: '\\x1b[1;36m',\n brightWhite: '\\x1b[1;37m',\n};\n\n// Safe execSync with strict timeout (returns empty string on failure)\nfunction safeExec(cmd, timeoutMs = 2000) {\n try {\n return execSync(cmd, {\n encoding: 'utf-8',\n timeout: timeoutMs,\n stdio: ['pipe', 'pipe', 'pipe'],\n // #681: read-only polling must not take .git/index.lock (a kill leaves it stale)\n env: Object.assign({}, process.env, { GIT_OPTIONAL_LOCKS: '0' }),\n }).trim();\n } catch (err) {\n if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err);\n return '';\n }\n}\n\n// Safe JSON file reader (returns null on failure)\n// Refuses to load files > 10 MB to prevent OOM on corrupted/oversized stores.\nconst MAX_JSON_READ_BYTES = 10 * 1024 * 1024;\nfunction readJSON(filePath) {\n try {\n if (fs.existsSync(filePath) && fs.statSync(filePath).size <= MAX_JSON_READ_BYTES) {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n return null;\n}\n\n// Safe file stat (returns null on failure)\nfunction safeStat(filePath) {\n try {\n return fs.statSync(filePath);\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n return null;\n}\n\n// Shared settings cache \u2014 read once, used by multiple functions\nlet _settingsCache = undefined;\nfunction getSettings() {\n if (_settingsCache !== undefined) return _settingsCache;\n _settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))\n || readJSON(path.join(CWD, '.claude', 'settings.local.json'))\n || null;\n return _settingsCache;\n}\n\n// Project identifier \u2014 github owner/repo from git remote, else folder name\nfunction getProjectName() {\n try {\n const remote = cached('gitRemote', TTL.slow, () => safeExec('git remote get-url origin 2>/dev/null', 2000)).trim();\n if (remote) {\n const m = remote.match(/[/:]([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/);\n if (m) return `${m[1]}/${m[2]}`;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n return path.basename(CWD);\n}\n\n// \u2500\u2500\u2500 Data Collection (all pure-Node.js or single-exec) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Get all git info in ONE shell call (cached for TTL.git)\nfunction getGitInfo() {\n return cached('git', TTL.git, readGitInfo);\n}\n\nfunction readGitInfo() {\n const result = {\n name: 'user', gitBranch: '', modified: 0, untracked: 0,\n staged: 0, ahead: 0, behind: 0,\n };\n\n // Single shell: get user.name, branch, porcelain status, and upstream diff\n const script = [\n 'git config user.name 2>/dev/null || echo user',\n 'echo \"---SEP---\"',\n 'git branch --show-current 2>/dev/null',\n 'echo \"---SEP---\"',\n 'git status --porcelain 2>/dev/null',\n 'echo \"---SEP---\"',\n 'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo \"0 0\"',\n ].join('; ');\n\n const raw = safeExec(`sh -c '${script}'`, 3000);\n if (!raw) return result;\n\n const parts = raw.split('---SEP---').map(s => s.trim());\n if (parts.length >= 4) {\n result.name = parts[0] || 'user';\n result.gitBranch = parts[1] || '';\n\n // Parse porcelain status\n if (parts[2]) {\n for (const line of parts[2].split('\\n')) {\n if (!line || line.length < 2) continue;\n const x = line[0], y = line[1];\n if (x === '?' && y === '?') { result.untracked++; continue; }\n if (x !== ' ' && x !== '?') result.staged++;\n if (y !== ' ' && y !== '?') result.modified++;\n }\n }\n\n // Parse ahead/behind\n const ab = (parts[3] || '0 0').split(/\\s+/);\n result.ahead = parseInt(ab[0]) || 0;\n result.behind = parseInt(ab[1]) || 0;\n }\n\n return result;\n}\n\n// Normalise a model ID string to a short display name\nfunction modelLabel(id) {\n // Derive the version from the id (claude-sonnet-5 -> \"Sonnet 5\", claude-opus-4-6 -> \"Opus 4.6\")\n // instead of assuming every sonnet/opus id is one fixed release.\n const m = id.match(/(fable|opus|sonnet|haiku)(?:-(\\d+)(?:-(\\d{1,2}))?(?!\\d))?/);\n if (m) {\n const family = m[1][0].toUpperCase() + m[1].slice(1);\n if (!m[2]) return family;\n return family + ' ' + (m[3] ? m[2] + '.' + m[3] : m[2]);\n }\n return id.split('-').slice(1, 3).join(' ');\n}\n\n// Read the last assistant model from the most recent session JSONL.\n// Claude Code writes each assistant turn to ~/.claude/projects//.jsonl\n// with a \"message.model\" field \u2014 this is the most accurate live source and\n// correctly reflects /model session overrides.\nfunction getModelFromSessionJSONL() {\n try {\n // Escape CWD the same way Claude Code does: replace '/' with '-'\n const escaped = CWD.replace(/\\//g, '-');\n const projectsDir = path.join(os.homedir(), '.claude', 'projects', escaped);\n if (!fs.existsSync(projectsDir)) return null;\n\n // Most recently modified JSONL = current (or latest) session\n const files = fs.readdirSync(projectsDir)\n .filter(f => f.endsWith('.jsonl'))\n .map(f => ({ f, mt: (() => { try { return fs.statSync(path.join(projectsDir, f)).mtimeMs; } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); return 0; } })() }))\n .sort((a, b) => b.mt - a.mt);\n if (files.length === 0) return null;\n\n const sessionFile = path.join(projectsDir, files[0].f);\n const sessionStat = safeStat(sessionFile);\n if (!sessionStat || sessionStat.size > 50 * 1024 * 1024) return null; // skip > 50 MB\n const raw = fs.readFileSync(sessionFile, 'utf-8');\n const lines = raw.split('\\n').filter(Boolean);\n\n // Scan from the end to find the most recent assistant model\n for (let i = lines.length - 1; i >= 0; i--) {\n try {\n const entry = JSON.parse(lines[i]);\n const model = entry?.message?.model || entry?.model;\n if (model && typeof model === 'string' && model.startsWith('claude')) {\n return model;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* skip malformed line */ }\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n return null;\n}\n\n// Detect model name from Claude config (pure file reads, no exec)\nfunction getModelName() {\n // PRIMARY: scan the live session JSONL \u2014 reflects /model overrides in real time\n const sessionModel = getModelFromSessionJSONL();\n if (sessionModel) return modelLabel(sessionModel);\n\n // SECONDARY: ~/.claude.json lastModelUsage for this exact project path\n // (longest-prefix match to avoid short paths like /Users matching first)\n try {\n const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json'));\n if (claudeConfig?.projects) {\n let bestMatch = null;\n let bestLen = -1;\n for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {\n if (CWD === projectPath || CWD.startsWith(projectPath + '/')) {\n if (projectPath.length > bestLen) {\n bestLen = projectPath.length;\n bestMatch = projectConfig;\n }\n }\n }\n if (bestMatch?.lastModelUsage) {\n const usage = bestMatch.lastModelUsage;\n const ids = Object.keys(usage);\n if (ids.length > 0) {\n let bestId = ids[ids.length - 1];\n let bestTokens = -1;\n for (const id of ids) {\n const e = usage[id] || {};\n const tokens = (e.inputTokens || 0) + (e.outputTokens || 0);\n if (tokens > bestTokens) { bestTokens = tokens; bestId = id; }\n }\n return modelLabel(bestId);\n }\n }\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n\n // TERTIARY: settings.json model field (configured default, not live session).\n const settings = getSettings();\n if (settings?.model) return modelLabel(settings.model);\n\n // QUATERNARY: read ANTHROPIC_MODEL or CLAUDE_MODEL env var (set by the CLI at launch)\n const envModel = process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || process.env.MODEL;\n if (envModel && envModel.startsWith('claude')) return modelLabel(envModel);\n\n // QUINARY: current model from the model ID in the env injected by Claude Code itself\n const claudeModel = process.env.CLAUDE_CODE_MODEL;\n if (claudeModel) return modelLabel(claudeModel);\n\n return 'Sonnet 5'; // known current default rather than the generic \"Claude Code\"\n}\n\n// Get learning stats from memory database (pure stat calls)\nfunction getLearningStats() {\n const memoryPaths = [\n path.join(CWD, '.swarm', 'memory.db'),\n path.join(CWD, '.monomind', 'memory.db'),\n path.join(CWD, '.claude', 'memory.db'),\n path.join(CWD, 'data', 'memory.db'),\n path.join(CWD, '.swarm', 'lancedb'),\n ];\n\n for (const dbPath of memoryPaths) {\n const stat = safeStat(dbPath);\n if (stat) {\n const sizeKB = stat.size / 1024;\n const patterns = Math.floor(sizeKB / 2);\n return {\n patterns,\n sessions: Math.max(1, Math.floor(patterns / 10)),\n };\n }\n }\n\n // Check session files count\n let sessions = 0;\n try {\n const sessDir = path.join(CWD, '.claude', 'sessions');\n if (fs.existsSync(sessDir)) {\n sessions = fs.readdirSync(sessDir).filter(f => f.endsWith('.json')).length;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n\n return { patterns: 0, sessions };\n}\n\n// progress from metrics files (pure file reads)\nfunction getv1Progress() {\n const learning = getLearningStats();\n const totalDomains = 5;\n\n const dddData = readJSON(path.join(CWD, '.monomind', 'metrics', 'ddd-progress.json'));\n let dddProgress = dddData?.progress || 0;\n let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));\n\n if (dddProgress === 0 && learning.patterns > 0) {\n if (learning.patterns >= 500) domainsCompleted = 5;\n else if (learning.patterns >= 200) domainsCompleted = 4;\n else if (learning.patterns >= 100) domainsCompleted = 3;\n else if (learning.patterns >= 50) domainsCompleted = 2;\n else if (learning.patterns >= 10) domainsCompleted = 1;\n dddProgress = Math.floor((domainsCompleted / totalDomains) * 100);\n }\n\n return {\n domainsCompleted, totalDomains, dddProgress,\n patternsLearned: learning.patterns,\n sessionsCompleted: learning.sessions,\n };\n}\n\n// Security status (pure file reads)\nfunction getSecurityStatus() {\n const auditData = readJSON(path.join(CWD, '.monomind', 'security', 'audit-status.json'));\n if (auditData) {\n const auditDate = auditData.lastAudit || auditData.lastScan;\n if (!auditDate) {\n // No audit has ever run \u2014 show as pending, not stale\n return { status: 'PENDING', cvesFixed: 0, totalCves: 0 };\n }\n const auditAge = Date.now() - new Date(auditDate).getTime();\n const isStale = auditAge > 7 * 24 * 60 * 60 * 1000;\n return {\n status: isStale ? 'STALE' : (auditData.status || 'PENDING'),\n cvesFixed: auditData.cvesFixed || 0,\n totalCves: auditData.totalCves || 0,\n };\n }\n\n let scanCount = 0;\n try {\n const scanDir = path.join(CWD, '.claude', 'security-scans');\n if (fs.existsSync(scanDir)) {\n scanCount = fs.readdirSync(scanDir).filter(f => f.endsWith('.json')).length;\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* ignore */ }\n\n return {\n status: scanCount > 0 ? 'SCANNED' : 'NONE',\n cvesFixed: 0,\n totalCves: 0,\n };\n}\n\n// Swarm status (pure file reads, NO ps aux)\nfunction getMonoswarmStatus() {\n const staleThresholdMs = 5 * 60 * 1000;\n const agentRegTtlMs = 30 * 60 * 1000; // registration files expire after 30 min\n const now = Date.now();\n\n // PRIMARY: count live registration files written by SubagentStart hook\n // Each file = one active sub-agent. Stale files (>30 min) are ignored.\n const regDir = path.join(CWD, '.monomind', 'agents', 'registrations');\n if (fs.existsSync(regDir)) {\n try {\n const files = fs.readdirSync(regDir).filter(f => f.endsWith('.json'));\n const liveCount = files.filter(f => {\n try {\n return (now - fs.statSync(path.join(regDir, f)).mtimeMs) < agentRegTtlMs;\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); return false; }\n }).length;\n if (liveCount > 0) {\n return {\n activeAgents: liveCount,\n maxAgents: CONFIG.maxAgents,\n coordinationActive: true,\n };\n }\n } catch (err) { if (process.env.MONOMIND_DEBUG) console.error('[statusline]', err); /* fall through */ }\n }\n\n // SECONDARY: monoswarm-activity.json refreshed by post-task hook\n const activityData = readJSON(path.join(CWD, '.monomind', 'metrics', 'monoswarm-activity.json'));\n if (activityData?.monoswarm) {\n const updatedAt = activityData.timestamp || activityData.monoswarm.timestamp;\n const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;\n if (age < staleThresholdMs) {\n return {\n activeAgents: activityData.monoswarm.agent_count || 0,\n maxAgents: CONFIG.maxAgents,\n coordinationActive: activityData.monoswarm.coordination_active || activityData.monoswarm.active || false,\n };\n }\n }\n\n return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };\n}\n\n"; //# sourceMappingURL=statusline-script-data.d.ts.map