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;
}