From c8f46cfa3feb39cd7c31e01e855f1887d409e56b Mon Sep 17 00:00:00 2001 From: Jarrod Watts Date: Sat, 3 Jan 2026 17:38:26 +1100 Subject: [PATCH] feat(v2): enhanced config display with separate counters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split config counting: CLAUDE.md, rules, MCPs, hooks - Display distinct icons for each config type (📄 📜 🔌 🪝) - Read hooks count from settings.json - Remove ralph-loop local config file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .claude/ralph-loop.local.md | 69 -------------------------------- src/config-reader.ts | 78 +++++++++++++++++++++++++------------ src/index.ts | 7 ++-- src/render/session-line.ts | 12 +++++- src/types.ts | 2 + 5 files changed, 69 insertions(+), 99 deletions(-) delete mode 100644 .claude/ralph-loop.local.md diff --git a/.claude/ralph-loop.local.md b/.claude/ralph-loop.local.md deleted file mode 100644 index e908140..0000000 --- a/.claude/ralph-loop.local.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -active: true -iteration: 1 -max_iterations: 50 -completion_promise: "STATUSLINE V2 COMPLETE" -started_at: "2026-01-03T03:24:24Z" ---- - -Implement claude-hud v2 statusline architecture per the plan at ~/.claude/plans/moonlit-jingling-flurry.md - -## GOAL -Rewrite claude-hud from split-pane TUI to multi-line statusline. X-ray vision for Claude Code - show whats happening during long operations. - -## TARGET OUTPUT -[Opus] ████████░░ 45% | 📋 3 rules | 🔌 5 MCPs | ⏱️ 12m -◐ Edit: auth.ts | ✓ Read ×3 | ✓ Grep ×2 -◐ explore [haiku]: Finding auth code (2m 15s) -▸ Fix authentication bug (2/5) - -## PHASE 1: Setup -- Tag v1: git tag v1.0.0-split-pane -- Create branch: git checkout -b v2-statusline -- Clean out old files (hooks/, tui/, scripts/) -- Create new structure: src/index.ts, src/stdin.ts, src/transcript.ts, src/render/ -- Update plugin.json with statusLine config -- PUSH after phase - -## PHASE 2: Core Parsers -- stdin.ts: Parse Claudes JSON (model, context_window, transcript_path) -- transcript.ts: Parse JSONL for tools (tool_use/tool_result), agents (Task), todos (TodoWrite) -- Detect running tools = tool_use without matching tool_result -- Types for everything -- PUSH after phase - -## PHASE 3: Render Lines -- colors.ts: ANSI helpers (green, yellow, red, reset) -- session-line.ts: [Model] context bar % | rules | MCPs | duration -- tools-line.ts: Running tool + completed counts -- agents-line.ts: Type [model]: description (elapsed) -- todos-line.ts: Current todo (completed/total) -- index.ts: Orchestrate all lines, console.log each -- PUSH after phase - -## PHASE 4: Polish and Ship -- Error handling (graceful degradation) -- Config reader for MCP/rules counts -- Performance (cache transcript parsing) -- Test: build, then simulate stdin pipe -- Update README -- PUSH after phase - -## RULES -1. Commit after meaningful progress -2. PUSH after each PHASE completes -3. If stuck 3+ times on same issue, TODO comment and move on -4. No hooks needed - read transcript directly -5. Target <50ms render time -6. Zero config - must just work - -## COMPLETION -Output STATUSLINE V2 COMPLETE ONLY when: -- All 4 lines rendering with correct format -- Context bar with color thresholds (green/yellow/red) -- Tool activity showing ◐ running and ✓ completed -- Agent tracking with [model] and description -- Todo progress with (x/y) format -- plugin.json has statusLine config -- Builds successfully -- README updated with new architecture diff --git a/src/config-reader.ts b/src/config-reader.ts index 5af69fd..1933e19 100644 --- a/src/config-reader.ts +++ b/src/config-reader.ts @@ -2,20 +2,48 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -export async function countRules(cwd?: string): Promise { - let count = 0; +export interface ConfigCounts { + claudeMdCount: number; + rulesCount: number; + mcpCount: number; + hooksCount: number; +} + +export async function countConfigs(cwd?: string): Promise { + let claudeMdCount = 0; + let rulesCount = 0; + let mcpCount = 0; + let hooksCount = 0; const claudeDir = path.join(os.homedir(), '.claude'); const globalRulesDir = path.join(claudeDir, 'rules'); + const settingsPath = path.join(claudeDir, 'settings.json'); if (fs.existsSync(path.join(claudeDir, 'CLAUDE.md'))) { - count++; + claudeMdCount++; } if (fs.existsSync(globalRulesDir)) { try { const files = fs.readdirSync(globalRulesDir); - count += files.filter((f) => f.endsWith('.md')).length; + rulesCount += files.filter((f) => f.endsWith('.md')).length; + } catch { + // Ignore errors + } + } + + if (fs.existsSync(settingsPath)) { + try { + const content = fs.readFileSync(settingsPath, 'utf8'); + const settings = JSON.parse(content); + + if (settings.mcpServers && typeof settings.mcpServers === 'object') { + mcpCount += Object.keys(settings.mcpServers).length; + } + + if (settings.hooks && typeof settings.hooks === 'object') { + hooksCount += Object.keys(settings.hooks).length; + } } catch { // Ignore errors } @@ -23,38 +51,38 @@ export async function countRules(cwd?: string): Promise { if (cwd) { if (fs.existsSync(path.join(cwd, 'CLAUDE.md'))) { - count++; + claudeMdCount++; } const projectRulesDir = path.join(cwd, '.claude', 'rules'); if (fs.existsSync(projectRulesDir)) { try { const files = fs.readdirSync(projectRulesDir); - count += files.filter((f) => f.endsWith('.md')).length; + rulesCount += files.filter((f) => f.endsWith('.md')).length; + } catch { + // Ignore errors + } + } + + const projectSettingsPath = path.join(cwd, '.claude', 'settings.json'); + if (fs.existsSync(projectSettingsPath)) { + try { + const content = fs.readFileSync(projectSettingsPath, 'utf8'); + const settings = JSON.parse(content); + + if (settings.mcpServers && typeof settings.mcpServers === 'object') { + mcpCount += Object.keys(settings.mcpServers).length; + } + + if (settings.hooks && typeof settings.hooks === 'object') { + hooksCount += Object.keys(settings.hooks).length; + } } catch { // Ignore errors } } } - return count; + return { claudeMdCount, rulesCount, mcpCount, hooksCount }; } -export async function countMcpServers(): Promise { - let count = 0; - - const globalMcpPath = path.join(os.homedir(), '.claude', '.mcp.json'); - if (fs.existsSync(globalMcpPath)) { - try { - const content = fs.readFileSync(globalMcpPath, 'utf8'); - const config = JSON.parse(content); - if (config.mcpServers && typeof config.mcpServers === 'object') { - count += Object.keys(config.mcpServers).length; - } - } catch { - // Ignore errors - } - } - - return count; -} diff --git a/src/index.ts b/src/index.ts index 494c70c..54b0abc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { readStdin } from './stdin.js'; import { parseTranscript } from './transcript.js'; import { render } from './render/index.js'; -import { countRules, countMcpServers } from './config-reader.js'; +import { countConfigs } from './config-reader.js'; import type { RenderContext } from './types.js'; async function main(): Promise { @@ -16,16 +16,17 @@ async function main(): Promise { const transcriptPath = stdin.transcript_path ?? ''; const transcript = await parseTranscript(transcriptPath); - const rulesCount = await countRules(stdin.cwd); - const mcpCount = await countMcpServers(); + const { claudeMdCount, rulesCount, mcpCount, hooksCount } = await countConfigs(stdin.cwd); const sessionDuration = formatSessionDuration(transcript.sessionStart); const ctx: RenderContext = { stdin, transcript, + claudeMdCount, rulesCount, mcpCount, + hooksCount, sessionDuration, }; diff --git a/src/render/session-line.ts b/src/render/session-line.ts index 0cf1be9..910b22a 100644 --- a/src/render/session-line.ts +++ b/src/render/session-line.ts @@ -11,16 +11,24 @@ export function renderSessionLine(ctx: RenderContext): string { parts.push(`${cyan(`[${model}]`)} ${bar} ${getContextColor(percent)}${percent}%${RESET}`); + if (ctx.claudeMdCount > 0) { + parts.push(dim(`📄 ${ctx.claudeMdCount} CLAUDE.md`)); + } + if (ctx.rulesCount > 0) { - parts.push(dim(`📋 ${ctx.rulesCount} rules`)); + parts.push(dim(`📜 ${ctx.rulesCount} rules`)); } if (ctx.mcpCount > 0) { parts.push(dim(`🔌 ${ctx.mcpCount} MCPs`)); } + if (ctx.hooksCount > 0) { + parts.push(dim(`🪝 ${ctx.hooksCount} hooks`)); + } + if (ctx.sessionDuration) { - parts.push(dim(`⏱️ ${ctx.sessionDuration}`)); + parts.push(dim(`⏱️ ${ctx.sessionDuration}`)); } let line = parts.join(' | '); diff --git a/src/types.ts b/src/types.ts index 9ac90ed..edcc5bf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,7 +60,9 @@ export interface TranscriptData { export interface RenderContext { stdin: StdinData; transcript: TranscriptData; + claudeMdCount: number; rulesCount: number; mcpCount: number; + hooksCount: number; sessionDuration: string; }