devflow-kit
Advanced tools
| import { Command } from 'commander'; | ||
| /** | ||
| * Add the HUD statusLine to settings JSON. | ||
| * Idempotent — returns unchanged JSON if HUD already set. | ||
| * Upgrades legacy statusline.sh to hud.sh automatically. | ||
| */ | ||
| export declare function addHudStatusLine(settingsJson: string, devflowDir: string): string; | ||
| /** | ||
| * Remove the HUD statusLine from settings JSON. | ||
| * Idempotent — returns unchanged JSON if statusLine not present or not DevFlow. | ||
| */ | ||
| export declare function removeHudStatusLine(settingsJson: string): string; | ||
| /** | ||
| * Check if the statusLine in settings JSON points to the DevFlow HUD. | ||
| */ | ||
| export declare function hasHudStatusLine(settingsJson: string): boolean; | ||
| /** | ||
| * Check if an existing statusLine belongs to a non-DevFlow tool. | ||
| */ | ||
| export declare function hasNonDevFlowStatusLine(settingsJson: string): boolean; | ||
| export declare const hudCommand: Command; | ||
| //# sourceMappingURL=hud.d.ts.map |
| import { Command } from 'commander'; | ||
| import { promises as fs } from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import * as p from '@clack/prompts'; | ||
| import color from 'picocolors'; | ||
| import { getClaudeDirectory, getDevFlowDirectory } from '../utils/paths.js'; | ||
| import { HUD_COMPONENTS, loadConfig, saveConfig, } from '../hud/config.js'; | ||
| /** | ||
| * Add the HUD statusLine to settings JSON. | ||
| * Idempotent — returns unchanged JSON if HUD already set. | ||
| * Upgrades legacy statusline.sh to hud.sh automatically. | ||
| */ | ||
| export function addHudStatusLine(settingsJson, devflowDir) { | ||
| const settings = JSON.parse(settingsJson); | ||
| const hudCommand = path.join(devflowDir, 'scripts', 'hud.sh'); | ||
| // Already pointing to this exact HUD — nothing to do | ||
| if (settings.statusLine?.command === hudCommand) { | ||
| return settingsJson; | ||
| } | ||
| // If there's a non-DevFlow statusLine, don't overwrite (caller should check first) | ||
| if (settings.statusLine && !isDevFlowStatusLine(settings.statusLine)) { | ||
| return settingsJson; | ||
| } | ||
| settings.statusLine = { | ||
| type: 'command', | ||
| command: hudCommand, | ||
| }; | ||
| return JSON.stringify(settings, null, 2) + '\n'; | ||
| } | ||
| /** | ||
| * Remove the HUD statusLine from settings JSON. | ||
| * Idempotent — returns unchanged JSON if statusLine not present or not DevFlow. | ||
| */ | ||
| export function removeHudStatusLine(settingsJson) { | ||
| const settings = JSON.parse(settingsJson); | ||
| if (!settings.statusLine) { | ||
| return settingsJson; | ||
| } | ||
| // Only remove if it's a DevFlow HUD/statusline | ||
| if (!isDevFlowStatusLine(settings.statusLine)) { | ||
| return settingsJson; | ||
| } | ||
| delete settings.statusLine; | ||
| return JSON.stringify(settings, null, 2) + '\n'; | ||
| } | ||
| /** | ||
| * Check if the statusLine in settings JSON points to the DevFlow HUD. | ||
| */ | ||
| export function hasHudStatusLine(settingsJson) { | ||
| const settings = JSON.parse(settingsJson); | ||
| if (!settings.statusLine) | ||
| return false; | ||
| return isDevFlowStatusLine(settings.statusLine); | ||
| } | ||
| /** | ||
| * Check if an existing statusLine belongs to DevFlow (HUD or legacy statusline). | ||
| * Matches paths containing 'hud.sh', 'statusline.sh', or a '/devflow/' directory segment. | ||
| */ | ||
| function isDevFlowStatusLine(statusLine) { | ||
| const cmd = statusLine.command ?? ''; | ||
| return (cmd.includes('hud.sh') || | ||
| cmd.includes('statusline.sh') || | ||
| cmd.includes('/devflow/') || | ||
| cmd.includes('\\devflow\\')); | ||
| } | ||
| /** | ||
| * Check if an existing statusLine belongs to a non-DevFlow tool. | ||
| */ | ||
| export function hasNonDevFlowStatusLine(settingsJson) { | ||
| const settings = JSON.parse(settingsJson); | ||
| if (!settings.statusLine?.command) | ||
| return false; | ||
| return !isDevFlowStatusLine(settings.statusLine); | ||
| } | ||
| export const hudCommand = new Command('hud') | ||
| .description('Configure the HUD (status line)') | ||
| .option('--status', 'Show current HUD config') | ||
| .option('--detail', 'Show tool/agent descriptions in HUD') | ||
| .option('--no-detail', 'Hide tool/agent descriptions') | ||
| .option('--enable', 'Enable HUD in settings') | ||
| .option('--disable', 'Disable HUD (remove statusLine)') | ||
| .action(async (options) => { | ||
| const hasFlag = options.status || | ||
| options.enable || | ||
| options.disable || | ||
| options.detail !== undefined; | ||
| if (!hasFlag) { | ||
| p.intro(color.bgCyan(color.white(' HUD '))); | ||
| p.note(`${color.cyan('devflow hud --detail')} Show tool/agent descriptions\n` + | ||
| `${color.cyan('devflow hud --no-detail')} Hide tool/agent descriptions\n` + | ||
| `${color.cyan('devflow hud --status')} Show current config\n` + | ||
| `${color.cyan('devflow hud --enable')} Enable HUD in settings\n` + | ||
| `${color.cyan('devflow hud --disable')} Remove HUD from settings`, 'Usage'); | ||
| p.note(`${HUD_COMPONENTS.length} components: ${HUD_COMPONENTS.join(', ')}`, 'Components'); | ||
| p.outro(color.dim('Toggle with --enable / --disable')); | ||
| return; | ||
| } | ||
| if (options.status) { | ||
| const config = loadConfig(); | ||
| p.intro(color.bgCyan(color.white(' HUD Status '))); | ||
| p.note(`${color.dim('Enabled:')} ${config.enabled ? color.green('yes') : color.dim('no')}\n` + | ||
| `${color.dim('Detail:')} ${config.detail ? color.green('on') : color.dim('off')}\n` + | ||
| `${color.dim('Components:')} ${HUD_COMPONENTS.length}`, 'Current config'); | ||
| // Check settings.json | ||
| const claudeDir = getClaudeDirectory(); | ||
| const settingsPath = path.join(claudeDir, 'settings.json'); | ||
| try { | ||
| const content = await fs.readFile(settingsPath, 'utf-8'); | ||
| const enabled = hasHudStatusLine(content); | ||
| p.log.info(`Status line: ${enabled ? color.green('enabled') : color.dim('disabled')}`); | ||
| } | ||
| catch { | ||
| p.log.info(`Status line: ${color.dim('no settings.json found')}`); | ||
| } | ||
| return; | ||
| } | ||
| if (options.detail !== undefined) { | ||
| const config = loadConfig(); | ||
| config.detail = options.detail; | ||
| saveConfig(config); | ||
| p.log.success(`HUD detail ${config.detail ? 'enabled' : 'disabled'}`); | ||
| return; | ||
| } | ||
| if (options.enable) { | ||
| const claudeDir = getClaudeDirectory(); | ||
| const settingsPath = path.join(claudeDir, 'settings.json'); | ||
| let settingsContent; | ||
| try { | ||
| settingsContent = await fs.readFile(settingsPath, 'utf-8'); | ||
| } | ||
| catch { | ||
| settingsContent = '{}'; | ||
| } | ||
| // Ensure statusLine is registered | ||
| if (!hasHudStatusLine(settingsContent)) { | ||
| // Check for non-DevFlow statusLine | ||
| if (hasNonDevFlowStatusLine(settingsContent)) { | ||
| const settings = JSON.parse(settingsContent); | ||
| p.log.warn(`Existing statusLine found: ${color.dim(settings.statusLine?.command ?? 'unknown')}`); | ||
| if (process.stdin.isTTY) { | ||
| const overwrite = await p.confirm({ | ||
| message: 'Replace existing statusLine with DevFlow HUD?', | ||
| initialValue: false, | ||
| }); | ||
| if (p.isCancel(overwrite) || !overwrite) { | ||
| p.log.info('HUD not enabled — existing statusLine preserved'); | ||
| return; | ||
| } | ||
| } | ||
| else { | ||
| p.log.info('Non-interactive mode — skipping (existing statusLine would be overwritten)'); | ||
| return; | ||
| } | ||
| } | ||
| const devflowDir = getDevFlowDirectory(); | ||
| const updated = addHudStatusLine(settingsContent, devflowDir); | ||
| await fs.writeFile(settingsPath, updated, 'utf-8'); | ||
| } | ||
| // Update config | ||
| const config = loadConfig(); | ||
| if (config.enabled) { | ||
| p.log.info('HUD already enabled'); | ||
| return; | ||
| } | ||
| saveConfig({ ...config, enabled: true }); | ||
| p.log.success('HUD enabled'); | ||
| p.log.info(color.dim('Restart Claude Code to see the HUD')); | ||
| } | ||
| if (options.disable) { | ||
| const config = loadConfig(); | ||
| if (!config.enabled) { | ||
| p.log.info('HUD already disabled'); | ||
| return; | ||
| } | ||
| saveConfig({ ...config, enabled: false }); | ||
| p.log.success('HUD disabled'); | ||
| p.log.info(color.dim('Version upgrade notifications will still appear')); | ||
| } | ||
| }); | ||
| //# sourceMappingURL=hud.js.map |
| export declare function getCacheDir(): string; | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| */ | ||
| export declare function readCache<T>(key: string): T | null; | ||
| /** | ||
| * Read a cached value regardless of TTL (stale data). Returns null if missing. | ||
| */ | ||
| export declare function readCacheStale<T>(key: string): T | null; | ||
| /** | ||
| * Write a value to cache with a TTL in milliseconds. | ||
| */ | ||
| export declare function writeCache<T>(key: string, data: T, ttlMs: number): void; | ||
| //# sourceMappingURL=cache.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| export function getCacheDir() { | ||
| const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow'); | ||
| return path.join(devflowDir, 'cache'); | ||
| } | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| * When `ignoreExpiry` is true, returns data regardless of TTL (stale read). | ||
| */ | ||
| function readCacheEntry(key, ignoreExpiry) { | ||
| try { | ||
| const filePath = path.join(getCacheDir(), `${key}.json`); | ||
| const raw = fs.readFileSync(filePath, 'utf-8'); | ||
| const entry = JSON.parse(raw); | ||
| if (ignoreExpiry || Date.now() - entry.timestamp < entry.ttl) { | ||
| return entry.data; | ||
| } | ||
| return null; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| */ | ||
| export function readCache(key) { | ||
| return readCacheEntry(key, false); | ||
| } | ||
| /** | ||
| * Read a cached value regardless of TTL (stale data). Returns null if missing. | ||
| */ | ||
| export function readCacheStale(key) { | ||
| return readCacheEntry(key, true); | ||
| } | ||
| /** | ||
| * Write a value to cache with a TTL in milliseconds. | ||
| */ | ||
| export function writeCache(key, data, ttlMs) { | ||
| try { | ||
| const dir = getCacheDir(); | ||
| if (!fs.existsSync(dir)) { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } | ||
| const entry = { data, timestamp: Date.now(), ttl: ttlMs }; | ||
| fs.writeFileSync(path.join(dir, `${key}.json`), JSON.stringify(entry)); | ||
| } | ||
| catch { | ||
| // Cache write failure is non-fatal | ||
| } | ||
| } | ||
| //# sourceMappingURL=cache.js.map |
| /** | ||
| * ANSI color helpers — no dependencies, precompiled escape sequences. | ||
| * Used by HUD components for direct terminal output (not @clack/prompts). | ||
| */ | ||
| export declare function bold(s: string): string; | ||
| export declare function dim(s: string): string; | ||
| export declare function red(s: string): string; | ||
| export declare function green(s: string): string; | ||
| export declare function yellow(s: string): string; | ||
| export declare function blue(s: string): string; | ||
| export declare function magenta(s: string): string; | ||
| export declare function cyan(s: string): string; | ||
| export declare function gray(s: string): string; | ||
| export declare function white(s: string): string; | ||
| export declare function orange(s: string): string; | ||
| export declare function brightRed(s: string): string; | ||
| export declare function boldRed(s: string): string; | ||
| export declare function bgGreen(s: string): string; | ||
| export declare function bgYellow(s: string): string; | ||
| export declare function bgRed(s: string): string; | ||
| export declare function truncate(s: string, max: number): string; | ||
| export declare function stripAnsi(s: string): string; | ||
| //# sourceMappingURL=colors.d.ts.map |
| /** | ||
| * ANSI color helpers — no dependencies, precompiled escape sequences. | ||
| * Used by HUD components for direct terminal output (not @clack/prompts). | ||
| */ | ||
| const ESC = '\x1b['; | ||
| const RESET = `${ESC}0m`; | ||
| export function bold(s) { | ||
| return `${ESC}1m${s}${RESET}`; | ||
| } | ||
| export function dim(s) { | ||
| return `${ESC}2m${s}${RESET}`; | ||
| } | ||
| export function red(s) { | ||
| return `${ESC}31m${s}${RESET}`; | ||
| } | ||
| export function green(s) { | ||
| return `${ESC}32m${s}${RESET}`; | ||
| } | ||
| export function yellow(s) { | ||
| return `${ESC}33m${s}${RESET}`; | ||
| } | ||
| export function blue(s) { | ||
| return `${ESC}34m${s}${RESET}`; | ||
| } | ||
| export function magenta(s) { | ||
| return `${ESC}35m${s}${RESET}`; | ||
| } | ||
| export function cyan(s) { | ||
| return `${ESC}36m${s}${RESET}`; | ||
| } | ||
| export function gray(s) { | ||
| return `${ESC}90m${s}${RESET}`; | ||
| } | ||
| export function white(s) { | ||
| return `${ESC}37m${s}${RESET}`; | ||
| } | ||
| export function orange(s) { | ||
| return `${ESC}38;5;208m${s}${RESET}`; | ||
| } | ||
| export function brightRed(s) { | ||
| return `${ESC}91m${s}${RESET}`; | ||
| } | ||
| export function boldRed(s) { | ||
| return `${ESC}1;31m${s}${RESET}`; | ||
| } | ||
| export function bgGreen(s) { | ||
| return `${ESC}42m${s}${RESET}`; | ||
| } | ||
| export function bgYellow(s) { | ||
| return `${ESC}43m${s}${RESET}`; | ||
| } | ||
| export function bgRed(s) { | ||
| return `${ESC}41m${s}${RESET}`; | ||
| } | ||
| export function truncate(s, max) { | ||
| return s.length > max ? s.slice(0, max - 1) + '\u2026' : s; | ||
| } | ||
| const ANSI_PATTERN = /\x1b\[[0-9;]*m/g; | ||
| export function stripAnsi(s) { | ||
| return s.replace(ANSI_PATTERN, ''); | ||
| } | ||
| //# sourceMappingURL=colors.js.map |
| import type { ComponentResult, GatherContext, ConfigCountsData } from '../types.js'; | ||
| /** | ||
| * Gather configuration counts for the configCounts component. | ||
| * Exported for use by the main HUD entry point. | ||
| */ | ||
| export declare function gatherConfigCounts(cwd: string): ConfigCountsData; | ||
| export default function configCounts(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=config-counts.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { dim } from '../colors.js'; | ||
| function countClaudeMdFiles(cwd) { | ||
| let count = 0; | ||
| // Check project CLAUDE.md | ||
| if (fs.existsSync(path.join(cwd, 'CLAUDE.md'))) | ||
| count++; | ||
| // Check user CLAUDE.md | ||
| const claudeDir = process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude'); | ||
| if (fs.existsSync(path.join(claudeDir, 'CLAUDE.md'))) | ||
| count++; | ||
| return count; | ||
| } | ||
| function countFromSettings(settingsPath) { | ||
| try { | ||
| const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); | ||
| const mcpServers = settings.mcpServers | ||
| ? Object.keys(settings.mcpServers).length | ||
| : 0; | ||
| let hooks = 0; | ||
| if (settings.hooks) { | ||
| const hooksObj = settings.hooks; | ||
| for (const event of Object.values(hooksObj)) { | ||
| if (Array.isArray(event)) | ||
| hooks += event.length; | ||
| } | ||
| } | ||
| return { mcpServers, hooks }; | ||
| } | ||
| catch { | ||
| return { mcpServers: 0, hooks: 0 }; | ||
| } | ||
| } | ||
| /** | ||
| * Gather configuration counts for the configCounts component. | ||
| * Exported for use by the main HUD entry point. | ||
| */ | ||
| export function gatherConfigCounts(cwd) { | ||
| const claudeDir = process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude'); | ||
| const claudeMdFiles = countClaudeMdFiles(cwd); | ||
| // Count rules (.md/.mdc files in .claude/rules) | ||
| let rules = 0; | ||
| for (const rulesDir of [ | ||
| path.join(cwd, '.claude', 'rules'), | ||
| path.join(claudeDir, 'rules'), | ||
| ]) { | ||
| try { | ||
| const files = fs.readdirSync(rulesDir); | ||
| rules += files.filter((f) => f.endsWith('.md') || f.endsWith('.mdc')).length; | ||
| } | ||
| catch { | ||
| /* ignore */ | ||
| } | ||
| } | ||
| // Aggregate settings from user and project | ||
| const userSettings = countFromSettings(path.join(claudeDir, 'settings.json')); | ||
| const projectSettings = countFromSettings(path.join(cwd, '.claude', 'settings.json')); | ||
| return { | ||
| claudeMdFiles, | ||
| rules, | ||
| mcpServers: userSettings.mcpServers + projectSettings.mcpServers, | ||
| hooks: userSettings.hooks + projectSettings.hooks, | ||
| }; | ||
| } | ||
| export default async function configCounts(ctx) { | ||
| if (!ctx.configCounts) | ||
| return null; | ||
| const { claudeMdFiles, rules: ruleCount, mcpServers, hooks: hookCount } = ctx.configCounts; | ||
| const skillCount = ctx.transcript?.skills.length ?? 0; | ||
| const parts = []; | ||
| if (claudeMdFiles > 0) | ||
| parts.push(`${claudeMdFiles} CLAUDE.md`); | ||
| if (ruleCount > 0) | ||
| parts.push(`${ruleCount} rules`); | ||
| if (mcpServers > 0) | ||
| parts.push(`${mcpServers} MCPs`); | ||
| if (hookCount > 0) | ||
| parts.push(`${hookCount} hooks`); | ||
| if (skillCount > 0) | ||
| parts.push(`${skillCount} skills`); | ||
| if (parts.length === 0) | ||
| return null; | ||
| const raw = parts.join(' \u00B7 '); | ||
| const text = parts.map((p) => dim(p)).join(dim(' \u00B7 ')); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=config-counts.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| /** | ||
| * Context window usage component. | ||
| * Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red. | ||
| * At >85% appends token breakdown: (in: Nk). | ||
| */ | ||
| export default function contextUsage(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=context-usage.d.ts.map |
| import { dim, green, yellow, red } from '../colors.js'; | ||
| const BAR_WIDTH = 8; | ||
| /** | ||
| * Context window usage component. | ||
| * Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red. | ||
| * At >85% appends token breakdown: (in: Nk). | ||
| */ | ||
| export default async function contextUsage(ctx) { | ||
| const cw = ctx.stdin.context_window; | ||
| if (!cw) | ||
| return null; | ||
| let pct = null; | ||
| if (cw.used_percentage !== undefined) { | ||
| pct = Math.round(cw.used_percentage); | ||
| } | ||
| else if (cw.context_window_size && cw.current_usage?.input_tokens !== undefined) { | ||
| pct = Math.round((cw.current_usage.input_tokens / cw.context_window_size) * 100); | ||
| } | ||
| if (pct === null) | ||
| return null; | ||
| const filled = Math.round((pct / 100) * BAR_WIDTH); | ||
| const empty = BAR_WIDTH - filled; | ||
| let colorFn; | ||
| if (pct < 50) { | ||
| colorFn = green; | ||
| } | ||
| else if (pct < 80) { | ||
| colorFn = yellow; | ||
| } | ||
| else { | ||
| colorFn = red; | ||
| } | ||
| const filledBar = '\u2588'.repeat(filled); | ||
| const emptyBar = '\u2591'.repeat(empty); | ||
| let suffix = ''; | ||
| if (pct > 85 && cw.current_usage?.input_tokens !== undefined) { | ||
| const inK = Math.round(cw.current_usage.input_tokens / 1000); | ||
| suffix = ` (in: ${inK}k)`; | ||
| } | ||
| const raw = `Current Session ${filledBar}${emptyBar} ${pct}%${suffix}`; | ||
| const text = dim('Current Session ') + | ||
| colorFn(filledBar) + | ||
| dim(emptyBar) + | ||
| ' ' + | ||
| colorFn(`${pct}%`) + | ||
| (suffix ? dim(suffix) : ''); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=context-usage.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function diffStats(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=diff-stats.d.ts.map |
| import { dim, green, red } from '../colors.js'; | ||
| export default async function diffStats(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const { filesChanged, additions, deletions } = ctx.git; | ||
| if (filesChanged === 0 && additions === 0 && deletions === 0) | ||
| return null; | ||
| const filePart = filesChanged > 0 | ||
| ? `${filesChanged} file${filesChanged === 1 ? '' : 's'}` | ||
| : ''; | ||
| const lineParts = []; | ||
| if (additions > 0) | ||
| lineParts.push(`+${additions}`); | ||
| if (deletions > 0) | ||
| lineParts.push(`-${deletions}`); | ||
| const sections = []; | ||
| const rawSections = []; | ||
| if (filePart) { | ||
| sections.push(dim(filePart)); | ||
| rawSections.push(filePart); | ||
| } | ||
| if (lineParts.length > 0) { | ||
| const lineText = lineParts | ||
| .map((p) => (p.startsWith('+') ? green(p) : red(p))) | ||
| .join(' '); | ||
| sections.push(lineText); | ||
| rawSections.push(lineParts.join(' ')); | ||
| } | ||
| if (sections.length === 0) | ||
| return null; | ||
| return { | ||
| text: sections.join(dim(' \u00B7 ')), | ||
| raw: rawSections.join(' \u00B7 '), | ||
| }; | ||
| } | ||
| //# sourceMappingURL=diff-stats.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function directory(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=directory.d.ts.map |
| import { bold, white } from '../colors.js'; | ||
| import * as path from 'node:path'; | ||
| export default async function directory(ctx) { | ||
| const cwd = ctx.stdin.cwd; | ||
| if (!cwd) | ||
| return null; | ||
| const name = path.basename(cwd); | ||
| return { text: bold(white(name)), raw: name }; | ||
| } | ||
| //# sourceMappingURL=directory.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function gitAheadBehind(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=git-ahead-behind.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function gitAheadBehind(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const { ahead, behind } = ctx.git; | ||
| if (ahead === 0 && behind === 0) | ||
| return null; | ||
| const rawParts = []; | ||
| if (ahead > 0) | ||
| rawParts.push(`${ahead}\u2191`); | ||
| if (behind > 0) | ||
| rawParts.push(`${behind}\u2193`); | ||
| const raw = rawParts.join(' '); | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=git-ahead-behind.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function gitBranch(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=git-branch.d.ts.map |
| import { white, yellow, green } from '../colors.js'; | ||
| export default async function gitBranch(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const dirtyMark = ctx.git.dirty ? '*' : ''; | ||
| const stagedMark = ctx.git.staged ? '+' : ''; | ||
| const indicator = dirtyMark + stagedMark; | ||
| const text = white(ctx.git.branch) + | ||
| (dirtyMark ? yellow(dirtyMark) : '') + | ||
| (stagedMark ? green(stagedMark) : ''); | ||
| const raw = ctx.git.branch + indicator; | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=git-branch.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function model(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=model.d.ts.map |
| import { dim, white } from '../colors.js'; | ||
| export default async function model(ctx) { | ||
| const name = ctx.stdin.model?.display_name; | ||
| if (!name) | ||
| return null; | ||
| // Strip "Claude " prefix and trailing context info like "(1M context)" for brevity | ||
| const short = name | ||
| .replace(/^Claude\s+/i, '') | ||
| .replace(/\s*\(\d+[KkMm]\s*context\)\s*$/, ''); | ||
| const cwSize = ctx.stdin.context_window?.context_window_size; | ||
| let sizeStr = ''; | ||
| if (cwSize) { | ||
| sizeStr = | ||
| cwSize >= 1_000_000 | ||
| ? ` [${Math.round(cwSize / 1_000_000)}m]` | ||
| : ` [${Math.round(cwSize / 1000)}k]`; | ||
| } | ||
| const raw = short + sizeStr; | ||
| return { text: white(short) + (sizeStr ? dim(sizeStr) : ''), raw }; | ||
| } | ||
| //# sourceMappingURL=model.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function releaseInfo(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=release-info.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function releaseInfo(ctx) { | ||
| if (!ctx.git?.lastTag) | ||
| return null; | ||
| const { lastTag, commitsSinceTag } = ctx.git; | ||
| const raw = commitsSinceTag > 0 ? `${lastTag} +${commitsSinceTag}` : lastTag; | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=release-info.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function sessionCost(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=session-cost.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function sessionCost(ctx) { | ||
| const cost = ctx.stdin.cost?.total_cost_usd; | ||
| if (cost == null) | ||
| return null; | ||
| const formatted = `$${cost.toFixed(2)}`; | ||
| return { text: dim(formatted), raw: formatted }; | ||
| } | ||
| //# sourceMappingURL=session-cost.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function sessionDuration(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=session-duration.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function sessionDuration(ctx) { | ||
| if (!ctx.sessionStartTime) | ||
| return null; | ||
| const elapsed = Math.floor((Date.now() - ctx.sessionStartTime) / 1000); | ||
| const minutes = Math.floor(elapsed / 60); | ||
| const hours = Math.floor(minutes / 60); | ||
| let label; | ||
| if (hours > 0) { | ||
| label = `${hours}h ${minutes % 60}m`; | ||
| } | ||
| else { | ||
| label = `${minutes}m`; | ||
| } | ||
| const text = `\u23F1 ${label}`; | ||
| return { text: dim(text), raw: text }; | ||
| } | ||
| //# sourceMappingURL=session-duration.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function todoProgress(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=todo-progress.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function todoProgress(ctx) { | ||
| if (!ctx.transcript) | ||
| return null; | ||
| const { todos } = ctx.transcript; | ||
| if (todos.total === 0) | ||
| return null; | ||
| const label = `${todos.completed}/${todos.total} todos`; | ||
| return { text: dim(label), raw: label }; | ||
| } | ||
| //# sourceMappingURL=todo-progress.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function usageQuota(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=usage-quota.d.ts.map |
| import { green, yellow, red, dim } from '../colors.js'; | ||
| const BAR_WIDTH = 8; | ||
| function renderBar(percent) { | ||
| const filled = Math.round((percent / 100) * BAR_WIDTH); | ||
| const empty = BAR_WIDTH - filled; | ||
| let colorFn; | ||
| if (percent < 50) { | ||
| colorFn = green; | ||
| } | ||
| else if (percent < 80) { | ||
| colorFn = yellow; | ||
| } | ||
| else { | ||
| colorFn = red; | ||
| } | ||
| const filledBar = '\u2588'.repeat(filled); | ||
| const emptyBar = '\u2591'.repeat(empty); | ||
| const text = colorFn(filledBar) + | ||
| dim(emptyBar) + | ||
| ' ' + | ||
| colorFn(`${percent}%`); | ||
| const raw = `${filledBar}${emptyBar} ${percent}%`; | ||
| return { text, raw }; | ||
| } | ||
| export default async function usageQuota(ctx) { | ||
| if (!ctx.usage) | ||
| return null; | ||
| const { fiveHourPercent, sevenDayPercent } = ctx.usage; | ||
| const parts = []; | ||
| if (fiveHourPercent !== null) { | ||
| const bar = renderBar(Math.round(fiveHourPercent)); | ||
| parts.push({ text: dim('5h ') + bar.text, raw: `5h ${bar.raw}` }); | ||
| } | ||
| if (sevenDayPercent !== null) { | ||
| const bar = renderBar(Math.round(sevenDayPercent)); | ||
| parts.push({ text: dim('7d ') + bar.text, raw: `7d ${bar.raw}` }); | ||
| } | ||
| if (parts.length === 0) | ||
| return null; | ||
| const sep = dim(' \u00B7 '); | ||
| const text = dim('Session ') + parts.map((p) => p.text).join(sep); | ||
| const raw = 'Session ' + parts.map((p) => p.raw).join(' \u00B7 '); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=usage-quota.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function versionBadge(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=version-badge.d.ts.map |
| import { execFile } from 'node:child_process'; | ||
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { yellow } from '../colors.js'; | ||
| import { readCache, writeCache } from '../cache.js'; | ||
| const VERSION_CACHE_KEY = 'version-check'; | ||
| const VERSION_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours | ||
| function getCurrentVersion(devflowDir) { | ||
| // Try manifest.json first (most reliable for installed version) | ||
| try { | ||
| const manifestPath = path.join(devflowDir, 'manifest.json'); | ||
| const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); | ||
| if (typeof manifest.version === 'string') | ||
| return manifest.version; | ||
| } | ||
| catch { | ||
| // Fall through | ||
| } | ||
| // Try package.json as fallback | ||
| try { | ||
| const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'package.json'); | ||
| const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); | ||
| if (typeof pkg.version === 'string') | ||
| return pkg.version; | ||
| } | ||
| catch { | ||
| // Fall through | ||
| } | ||
| return null; | ||
| } | ||
| function fetchLatestVersion() { | ||
| return new Promise((resolve) => { | ||
| execFile('npm', ['view', 'devflow-kit', 'version', '--json'], { timeout: 5000 }, (err, stdout) => { | ||
| if (err) { | ||
| resolve(null); | ||
| return; | ||
| } | ||
| try { | ||
| const parsed = JSON.parse(stdout.trim()); | ||
| resolve(typeof parsed === 'string' ? parsed : null); | ||
| } | ||
| catch { | ||
| const trimmed = stdout.trim(); | ||
| resolve(trimmed || null); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| function compareVersions(current, latest) { | ||
| const a = current.split('.').map(Number); | ||
| const b = latest.split('.').map(Number); | ||
| for (let i = 0; i < 3; i++) { | ||
| if ((a[i] || 0) < (b[i] || 0)) | ||
| return -1; | ||
| if ((a[i] || 0) > (b[i] || 0)) | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } | ||
| export default async function versionBadge(ctx) { | ||
| const current = getCurrentVersion(ctx.devflowDir); | ||
| if (!current) | ||
| return null; | ||
| // Check cache | ||
| let info = readCache(VERSION_CACHE_KEY); | ||
| if (!info) { | ||
| const latest = await fetchLatestVersion(); | ||
| if (latest) { | ||
| info = { current, latest }; | ||
| writeCache(VERSION_CACHE_KEY, info, VERSION_CACHE_TTL); | ||
| } | ||
| } | ||
| if (info && compareVersions(info.current, info.latest) < 0) { | ||
| const badge = `\u2726 Devflow v${info.latest} \u00B7 update: npx devflow-kit init`; | ||
| return { text: yellow(badge), raw: badge }; | ||
| } | ||
| // Don't show version when up to date | ||
| return null; | ||
| } | ||
| //# sourceMappingURL=version-badge.js.map |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function worktreeCount(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=worktree-count.d.ts.map |
| import { dim } from '../colors.js'; | ||
| export default async function worktreeCount(ctx) { | ||
| if (!ctx.git || ctx.git.worktreeCount <= 1) | ||
| return null; | ||
| const raw = `${ctx.git.worktreeCount} worktrees`; | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=worktree-count.js.map |
| import type { HudConfig, ComponentId } from './types.js'; | ||
| /** | ||
| * All 14 HUD components in display order. | ||
| */ | ||
| export declare const HUD_COMPONENTS: readonly ComponentId[]; | ||
| export declare function getConfigPath(): string; | ||
| export declare function loadConfig(): HudConfig; | ||
| export declare function saveConfig(config: HudConfig): void; | ||
| export declare function resolveComponents(config: HudConfig): ComponentId[]; | ||
| //# sourceMappingURL=config.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| /** | ||
| * All 14 HUD components in display order. | ||
| */ | ||
| export const HUD_COMPONENTS = [ | ||
| 'directory', | ||
| 'gitBranch', | ||
| 'gitAheadBehind', | ||
| 'diffStats', | ||
| 'releaseInfo', | ||
| 'worktreeCount', | ||
| 'model', | ||
| 'contextUsage', | ||
| 'versionBadge', | ||
| 'sessionDuration', | ||
| 'sessionCost', | ||
| 'usageQuota', | ||
| 'todoProgress', | ||
| 'configCounts', | ||
| ]; | ||
| export function getConfigPath() { | ||
| const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow'); | ||
| return path.join(devflowDir, 'hud.json'); | ||
| } | ||
| export function loadConfig() { | ||
| const configPath = getConfigPath(); | ||
| try { | ||
| const raw = fs.readFileSync(configPath, 'utf-8'); | ||
| const parsed = JSON.parse(raw); | ||
| return { | ||
| enabled: parsed.enabled !== false, | ||
| detail: parsed.detail === true, | ||
| }; | ||
| } | ||
| catch { | ||
| return { enabled: true, detail: false }; | ||
| } | ||
| } | ||
| export function saveConfig(config) { | ||
| const configPath = getConfigPath(); | ||
| const dir = path.dirname(configPath); | ||
| if (!fs.existsSync(dir)) { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } | ||
| fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n'); | ||
| } | ||
| export function resolveComponents(config) { | ||
| if (config.enabled) | ||
| return [...HUD_COMPONENTS]; | ||
| // Version badge always renders so users see upgrade notifications | ||
| return ['versionBadge']; | ||
| } | ||
| //# sourceMappingURL=config.js.map |
| export interface OAuthCredentials { | ||
| accessToken: string; | ||
| subscriptionType?: string; | ||
| } | ||
| /** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */ | ||
| export declare function getClaudeDir(): string; | ||
| /** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */ | ||
| export declare function readCredentialsFile(claudeDir?: string): OAuthCredentials | null; | ||
| /** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */ | ||
| export declare function readKeychainToken(): Promise<string | null>; | ||
| /** | ||
| * Get OAuth credentials using platform-appropriate strategy. | ||
| * macOS: Keychain first, then file fallback. Other platforms: file only. | ||
| * Hybrid: if Keychain has token but no subscriptionType, merge from file. | ||
| */ | ||
| export declare function getCredentials(): Promise<OAuthCredentials | null>; | ||
| //# sourceMappingURL=credentials.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { execFile } from 'node:child_process'; | ||
| const KEYCHAIN_TIMEOUT = 3000; // 3s | ||
| const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG; | ||
| function debugLog(msg, data) { | ||
| if (!DEBUG) | ||
| return; | ||
| const entry = { ts: new Date().toISOString(), source: 'credentials', msg, ...data }; | ||
| fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n'); | ||
| } | ||
| /** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */ | ||
| export function getClaudeDir() { | ||
| return (process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude')); | ||
| } | ||
| /** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */ | ||
| export function readCredentialsFile(claudeDir) { | ||
| try { | ||
| const dir = claudeDir ?? getClaudeDir(); | ||
| const filePath = path.join(dir, '.credentials.json'); | ||
| const raw = fs.readFileSync(filePath, 'utf-8'); | ||
| const creds = JSON.parse(raw); | ||
| const oauth = creds.claudeAiOauth; | ||
| const accessToken = oauth?.accessToken; | ||
| if (typeof accessToken !== 'string' || !accessToken) | ||
| return null; | ||
| const subscriptionType = typeof oauth?.subscriptionType === 'string' ? oauth.subscriptionType : undefined; | ||
| debugLog('credentials file read', { filePath, hasSubscriptionType: !!subscriptionType }); | ||
| return { accessToken, subscriptionType }; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| /** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */ | ||
| export function readKeychainToken() { | ||
| if (process.platform !== 'darwin') | ||
| return Promise.resolve(null); | ||
| return new Promise((resolve) => { | ||
| execFile('/usr/bin/security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: KEYCHAIN_TIMEOUT }, (err, stdout) => { | ||
| if (err || !stdout.trim()) { | ||
| debugLog('keychain read failed', { error: err?.message }); | ||
| resolve(null); | ||
| return; | ||
| } | ||
| try { | ||
| const parsed = JSON.parse(stdout.trim()); | ||
| const oauth = parsed.claudeAiOauth; | ||
| const token = oauth?.accessToken; | ||
| if (typeof token === 'string' && token) { | ||
| debugLog('keychain token found'); | ||
| resolve(token); | ||
| } | ||
| else { | ||
| debugLog('keychain: no accessToken in parsed data'); | ||
| resolve(null); | ||
| } | ||
| } | ||
| catch { | ||
| // Keychain value might be the raw token string | ||
| const trimmed = stdout.trim(); | ||
| if (trimmed.length > 20) { | ||
| debugLog('keychain: raw token string'); | ||
| resolve(trimmed); | ||
| } | ||
| else { | ||
| debugLog('keychain: unparseable value'); | ||
| resolve(null); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| /** | ||
| * Get OAuth credentials using platform-appropriate strategy. | ||
| * macOS: Keychain first, then file fallback. Other platforms: file only. | ||
| * Hybrid: if Keychain has token but no subscriptionType, merge from file. | ||
| */ | ||
| export async function getCredentials() { | ||
| const fileCreds = readCredentialsFile(); | ||
| if (process.platform !== 'darwin') { | ||
| debugLog('non-darwin: file credentials only', { found: !!fileCreds }); | ||
| return fileCreds; | ||
| } | ||
| // macOS: try Keychain first | ||
| const keychainToken = await readKeychainToken(); | ||
| if (keychainToken) { | ||
| // Merge subscriptionType from file if Keychain doesn't have it | ||
| const subscriptionType = fileCreds?.subscriptionType; | ||
| debugLog('using keychain token', { hasSubscriptionType: !!subscriptionType }); | ||
| return { accessToken: keychainToken, subscriptionType }; | ||
| } | ||
| // Fallback to file | ||
| debugLog('keychain failed, falling back to file', { found: !!fileCreds }); | ||
| return fileCreds; | ||
| } | ||
| //# sourceMappingURL=credentials.js.map |
| import type { GitStatus } from './types.js'; | ||
| /** | ||
| * Gather git status for the given working directory. | ||
| * Returns null if not in a git repo or on error. | ||
| */ | ||
| export declare function gatherGitStatus(cwd: string): Promise<GitStatus | null>; | ||
| //# sourceMappingURL=git.d.ts.map |
+153
| import { execFile } from 'node:child_process'; | ||
| const GIT_TIMEOUT = 1000; // 1s per command | ||
| function shellExec(cmd, args, cwd) { | ||
| return new Promise((resolve) => { | ||
| execFile(cmd, args, { cwd, timeout: GIT_TIMEOUT }, (err, stdout) => { | ||
| resolve(err ? '' : stdout.trim()); | ||
| }); | ||
| }); | ||
| } | ||
| function gitExec(args, cwd) { | ||
| return shellExec('git', args, cwd); | ||
| } | ||
| /** | ||
| * Gather git status for the given working directory. | ||
| * Returns null if not in a git repo or on error. | ||
| */ | ||
| export async function gatherGitStatus(cwd) { | ||
| // Check if in a git repo | ||
| const topLevel = await gitExec(['rev-parse', '--show-toplevel'], cwd); | ||
| if (!topLevel) | ||
| return null; | ||
| // Branch name | ||
| const branch = await gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], cwd); | ||
| if (!branch) | ||
| return null; | ||
| // Dirty check | ||
| const statusOutput = await gitExec(['status', '--porcelain', '--no-optional-locks'], cwd); | ||
| let dirty = false; | ||
| let staged = false; | ||
| for (const line of statusOutput.split('\n')) { | ||
| if (line.length < 2) | ||
| continue; | ||
| const index = line[0]; | ||
| const worktree = line[1]; | ||
| // Index column: staged change (A/M/D/R/C) | ||
| if (index !== ' ' && index !== '?') | ||
| staged = true; | ||
| // Worktree column: unstaged change (M/D), or untracked (??) | ||
| if (worktree !== ' ' || index === '?') | ||
| dirty = true; | ||
| } | ||
| // Ahead/behind — detect base branch with layered fallback (ported from statusline.sh) | ||
| const baseBranch = await detectBaseBranch(branch, cwd); | ||
| let ahead = 0; | ||
| let behind = 0; | ||
| if (baseBranch) { | ||
| const revList = await gitExec(['rev-list', '--left-right', '--count', `${baseBranch}...HEAD`], cwd); | ||
| const parts = revList.split(/\s+/); | ||
| if (parts.length === 2) { | ||
| behind = parseInt(parts[0], 10) || 0; | ||
| ahead = parseInt(parts[1], 10) || 0; | ||
| } | ||
| } | ||
| // Diff stats against base | ||
| let filesChanged = 0; | ||
| let additions = 0; | ||
| let deletions = 0; | ||
| if (baseBranch) { | ||
| const diffStat = await gitExec(['diff', '--shortstat', baseBranch], cwd); | ||
| const filesMatch = diffStat.match(/(\d+)\s+file/); | ||
| const addMatch = diffStat.match(/(\d+)\s+insertion/); | ||
| const delMatch = diffStat.match(/(\d+)\s+deletion/); | ||
| filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : 0; | ||
| additions = addMatch ? parseInt(addMatch[1], 10) : 0; | ||
| deletions = delMatch ? parseInt(delMatch[1], 10) : 0; | ||
| } | ||
| // Tag and worktree info (parallel) | ||
| const [tagOutput, worktreeOutput] = await Promise.all([ | ||
| gitExec(['describe', '--tags', '--abbrev=0'], cwd), | ||
| gitExec(['worktree', 'list'], cwd), | ||
| ]); | ||
| const lastTag = tagOutput || null; | ||
| let commitsSinceTag = 0; | ||
| if (lastTag) { | ||
| const countOutput = await gitExec(['rev-list', `${lastTag}..HEAD`, '--count'], cwd); | ||
| commitsSinceTag = parseInt(countOutput, 10) || 0; | ||
| } | ||
| const worktreeCount = worktreeOutput | ||
| ? worktreeOutput.split('\n').filter(l => l.trim().length > 0).length | ||
| : 1; | ||
| return { | ||
| branch, | ||
| dirty, | ||
| staged, | ||
| ahead, | ||
| behind, | ||
| filesChanged, | ||
| additions, | ||
| deletions, | ||
| lastTag, | ||
| commitsSinceTag, | ||
| worktreeCount, | ||
| }; | ||
| } | ||
| /** | ||
| * Detect the base branch for ahead/behind calculations. | ||
| * Uses a 4-layer fallback (ported from statusline.sh): | ||
| * 1. Branch reflog ("Created from") | ||
| * 2. HEAD reflog ("checkout: moving from X to branch") | ||
| * 3. GitHub PR base branch (gh pr view, cached) | ||
| * 4. main/master fallback | ||
| */ | ||
| async function detectBaseBranch(branch, cwd) { | ||
| // Layer 1: branch reflog — look for "branch: Created from" | ||
| const branchLog = await gitExec(['reflog', 'show', branch, '--format=%gs', '-n', '1'], cwd); | ||
| const createdMatch = branchLog.match(/branch: Created from (.+)/); | ||
| if (createdMatch) { | ||
| const candidate = createdMatch[1]; | ||
| if (candidate !== 'HEAD' && !candidate.includes('~')) { | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| } | ||
| // Layer 2: HEAD reflog — look for "checkout: moving from X to branch" | ||
| const headLog = await gitExec(['reflog', 'show', 'HEAD', '--format=%gs'], cwd); | ||
| const escapedBranch = branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| const checkoutPattern = new RegExp(`checkout: moving from (\\S+) to ${escapedBranch}`); | ||
| for (const line of headLog.split('\n')) { | ||
| const match = line.match(checkoutPattern); | ||
| if (match) { | ||
| const candidate = match[1]; | ||
| // Skip raw commit hashes and the current branch | ||
| if (candidate === branch || /^[0-9a-f]{7,}$/.test(candidate)) | ||
| continue; | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| } | ||
| // Layer 3: GitHub PR base branch via gh CLI | ||
| const prBase = await shellExec('gh', ['pr', 'view', '--json', 'baseRefName', '-q', '.baseRefName'], cwd); | ||
| if (prBase) { | ||
| const exists = await gitExec(['rev-parse', '--verify', prBase], cwd); | ||
| if (exists) | ||
| return prBase; | ||
| } | ||
| // Layer 4: main/master fallback (skip if already on that branch — use remote tracking instead) | ||
| for (const candidate of ['main', 'master']) { | ||
| if (candidate === branch) { | ||
| // On main/master itself — compare against remote tracking branch | ||
| const remote = await gitExec(['rev-parse', '--verify', `origin/${candidate}`], cwd); | ||
| if (remote) | ||
| return `origin/${candidate}`; | ||
| continue; | ||
| } | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| return null; | ||
| } | ||
| //# sourceMappingURL=git.js.map |
| export {}; | ||
| //# sourceMappingURL=index.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { readStdin } from './stdin.js'; | ||
| import { loadConfig, resolveComponents } from './config.js'; | ||
| import { gatherGitStatus } from './git.js'; | ||
| import { parseTranscript } from './transcript.js'; | ||
| import { fetchUsageData } from './usage-api.js'; | ||
| import { gatherConfigCounts } from './components/config-counts.js'; | ||
| import { render } from './render.js'; | ||
| const OVERALL_TIMEOUT = 2000; // 2 second overall timeout | ||
| async function main() { | ||
| const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT)); | ||
| try { | ||
| const result = await Promise.race([run(), timeoutPromise]); | ||
| process.stdout.write(result); | ||
| } | ||
| catch { | ||
| // Timeout or error — output nothing (graceful degradation) | ||
| } | ||
| } | ||
| async function run() { | ||
| const stdin = await readStdin(); | ||
| // Debug: dump raw stdin to file when DEVFLOW_HUD_DEBUG is set | ||
| if (process.env.DEVFLOW_HUD_DEBUG) { | ||
| fs.writeFileSync(process.env.DEVFLOW_HUD_DEBUG, JSON.stringify(stdin, null, 2)); | ||
| } | ||
| const config = loadConfig(); | ||
| const resolved = resolveComponents(config); | ||
| const components = new Set(resolved); | ||
| const cwd = stdin.cwd || process.cwd(); | ||
| const devflowDir = process.env.DEVFLOW_DIR || | ||
| path.join(process.env.HOME || homedir(), '.devflow'); | ||
| // Determine what data to gather based on enabled components | ||
| const needsGit = components.has('gitBranch') || | ||
| components.has('gitAheadBehind') || | ||
| components.has('diffStats') || | ||
| components.has('releaseInfo') || | ||
| components.has('worktreeCount'); | ||
| const needsTranscript = components.has('todoProgress') || | ||
| components.has('configCounts'); | ||
| const needsUsage = components.has('usageQuota'); | ||
| const needsConfigCounts = components.has('configCounts'); | ||
| // Parallel data gathering — only fetch what's needed | ||
| const [git, transcript, usage] = await Promise.all([ | ||
| needsGit ? gatherGitStatus(cwd) : Promise.resolve(null), | ||
| needsTranscript && stdin.transcript_path | ||
| ? parseTranscript(stdin.transcript_path) | ||
| : Promise.resolve(null), | ||
| needsUsage ? fetchUsageData() : Promise.resolve(null), | ||
| ]); | ||
| // Session start time from transcript file creation time | ||
| let sessionStartTime = null; | ||
| if (stdin.transcript_path) { | ||
| try { | ||
| const stat = fs.statSync(stdin.transcript_path); | ||
| sessionStartTime = stat.birthtime.getTime(); | ||
| } | ||
| catch { /* file may not exist yet */ } | ||
| } | ||
| // Config counts (fast, synchronous filesystem reads) | ||
| const configCountsData = needsConfigCounts | ||
| ? gatherConfigCounts(cwd) | ||
| : null; | ||
| // Terminal width via stderr (stdout is piped to Claude Code) | ||
| const terminalWidth = process.stderr.columns || 120; | ||
| const ctx = { | ||
| stdin, | ||
| git, | ||
| transcript, | ||
| usage, | ||
| configCounts: configCountsData, | ||
| config: { ...config, components: resolved }, | ||
| devflowDir, | ||
| sessionStartTime, | ||
| terminalWidth, | ||
| }; | ||
| return render(ctx); | ||
| } | ||
| main(); | ||
| //# sourceMappingURL=index.js.map |
| import type { GatherContext } from './types.js'; | ||
| /** | ||
| * Render all enabled components into a multi-line HUD string. | ||
| * Components that return null are excluded. Empty lines are skipped. | ||
| */ | ||
| export declare function render(ctx: GatherContext): Promise<string>; | ||
| //# sourceMappingURL=render.d.ts.map |
| import { dim } from './colors.js'; | ||
| import directory from './components/directory.js'; | ||
| import gitBranch from './components/git-branch.js'; | ||
| import gitAheadBehind from './components/git-ahead-behind.js'; | ||
| import diffStats from './components/diff-stats.js'; | ||
| import model from './components/model.js'; | ||
| import contextUsage from './components/context-usage.js'; | ||
| import versionBadge from './components/version-badge.js'; | ||
| import sessionDuration from './components/session-duration.js'; | ||
| import usageQuota from './components/usage-quota.js'; | ||
| import todoProgress from './components/todo-progress.js'; | ||
| import configCounts from './components/config-counts.js'; | ||
| import sessionCost from './components/session-cost.js'; | ||
| import releaseInfo from './components/release-info.js'; | ||
| import worktreeCount from './components/worktree-count.js'; | ||
| const COMPONENT_MAP = { | ||
| directory, | ||
| gitBranch, | ||
| gitAheadBehind, | ||
| diffStats, | ||
| model, | ||
| contextUsage, | ||
| versionBadge, | ||
| sessionDuration, | ||
| usageQuota, | ||
| todoProgress, | ||
| configCounts, | ||
| sessionCost, | ||
| releaseInfo, | ||
| worktreeCount, | ||
| }; | ||
| /** | ||
| * Line groupings for smart layout. | ||
| * Components are assigned to lines and only rendered if enabled. | ||
| * null entries denote section breaks (blank line between sections). | ||
| */ | ||
| const LINE_GROUPS = [ | ||
| // Section 1: Info (3 lines) | ||
| ['directory', 'gitBranch', 'gitAheadBehind', 'releaseInfo', 'worktreeCount', 'diffStats'], | ||
| ['contextUsage', 'usageQuota'], | ||
| ['model', 'sessionDuration', 'sessionCost', 'configCounts'], | ||
| // --- section break --- | ||
| null, | ||
| // Section 2: Activity | ||
| ['todoProgress'], | ||
| ['versionBadge'], | ||
| ]; | ||
| const SEPARATOR = dim(' \u00B7 '); | ||
| /** | ||
| * Render all enabled components into a multi-line HUD string. | ||
| * Components that return null are excluded. Empty lines are skipped. | ||
| */ | ||
| export async function render(ctx) { | ||
| const enabled = new Set(ctx.config.components); | ||
| // Render all enabled components in parallel | ||
| const results = new Map(); | ||
| const promises = []; | ||
| for (const id of enabled) { | ||
| const fn = COMPONENT_MAP[id]; | ||
| if (!fn) | ||
| continue; | ||
| promises.push(fn(ctx) | ||
| .then((result) => { | ||
| if (result) | ||
| results.set(id, result); | ||
| }) | ||
| .catch(() => { | ||
| /* Component failure is non-fatal */ | ||
| })); | ||
| } | ||
| await Promise.all(promises); | ||
| // Assemble lines using smart layout with section breaks | ||
| const lines = []; | ||
| let pendingBreak = false; | ||
| for (const entry of LINE_GROUPS) { | ||
| if (entry === null) { | ||
| if (lines.length > 0) | ||
| pendingBreak = true; | ||
| continue; | ||
| } | ||
| const lineResults = entry | ||
| .filter((id) => enabled.has(id) && results.has(id)) | ||
| .map((id) => results.get(id)); | ||
| if (lineResults.length > 0) { | ||
| if (pendingBreak) { | ||
| lines.push(''); | ||
| pendingBreak = false; | ||
| } | ||
| // Separate multi-line results (containing newlines) from single-line | ||
| const singleLine = []; | ||
| for (const r of lineResults) { | ||
| if (r.text.includes('\n')) { | ||
| // Flush any accumulated single-line parts first | ||
| if (singleLine.length > 0) { | ||
| lines.push(singleLine.join(SEPARATOR)); | ||
| singleLine.length = 0; | ||
| } | ||
| lines.push(r.text); | ||
| } | ||
| else { | ||
| singleLine.push(r.text); | ||
| } | ||
| } | ||
| if (singleLine.length > 0) { | ||
| lines.push(singleLine.join(SEPARATOR)); | ||
| } | ||
| } | ||
| } | ||
| return lines.join('\n'); | ||
| } | ||
| //# sourceMappingURL=render.js.map |
| import type { StdinData } from './types.js'; | ||
| /** | ||
| * Read and parse JSON from stdin. Returns empty object on parse failure. | ||
| */ | ||
| export declare function readStdin(): Promise<StdinData>; | ||
| //# sourceMappingURL=stdin.d.ts.map |
| /** | ||
| * Read and parse JSON from stdin. Returns empty object on parse failure. | ||
| */ | ||
| export function readStdin() { | ||
| return new Promise((resolve) => { | ||
| let data = ''; | ||
| process.stdin.setEncoding('utf-8'); | ||
| process.stdin.on('data', (chunk) => { | ||
| data += chunk; | ||
| }); | ||
| process.stdin.on('end', () => { | ||
| try { | ||
| resolve(JSON.parse(data)); | ||
| } | ||
| catch { | ||
| resolve({}); | ||
| } | ||
| }); | ||
| process.stdin.on('error', () => { | ||
| resolve({}); | ||
| }); | ||
| process.stdin.resume(); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=stdin.js.map |
| import type { TranscriptData } from './types.js'; | ||
| /** | ||
| * Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress. | ||
| * Returns null if the file doesn't exist or can't be parsed. | ||
| */ | ||
| export declare function parseTranscript(transcriptPath: string): Promise<TranscriptData | null>; | ||
| //# sourceMappingURL=transcript.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import * as readline from 'node:readline'; | ||
| // Precompiled patterns | ||
| const TODO_WRITE_NAME = /^TodoWrite$/i; | ||
| /** | ||
| * Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress. | ||
| * Returns null if the file doesn't exist or can't be parsed. | ||
| */ | ||
| export async function parseTranscript(transcriptPath) { | ||
| try { | ||
| if (!fs.existsSync(transcriptPath)) | ||
| return null; | ||
| const tools = new Map(); | ||
| const agents = new Map(); | ||
| const skills = new Set(); | ||
| let todosCompleted = 0; | ||
| let todosTotal = 0; | ||
| const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' }); | ||
| const rl = readline.createInterface({ | ||
| input: stream, | ||
| crlfDelay: Infinity, | ||
| }); | ||
| for await (const line of rl) { | ||
| if (!line.trim()) | ||
| continue; | ||
| try { | ||
| const entry = JSON.parse(line); | ||
| // Turn boundary: clear tools and agents on each human message | ||
| // so only the current turn's activity shows in the HUD | ||
| if (entry.type === 'human') { | ||
| tools.clear(); | ||
| agents.clear(); | ||
| continue; | ||
| } | ||
| const todoResult = processEntry(entry, tools, agents, skills); | ||
| if (todoResult) { | ||
| todosCompleted = todoResult.completed; | ||
| todosTotal = todoResult.total; | ||
| } | ||
| } | ||
| catch { | ||
| // Skip malformed lines | ||
| } | ||
| } | ||
| return { | ||
| tools: Array.from(tools.values()), | ||
| agents: Array.from(agents.values()), | ||
| todos: { completed: todosCompleted, total: todosTotal }, | ||
| skills: Array.from(skills), | ||
| }; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| function processEntry(entry, tools, agents, skills) { | ||
| if (entry.type !== 'assistant' || !entry.message) | ||
| return null; | ||
| const message = entry.message; | ||
| if (!Array.isArray(message.content)) | ||
| return null; | ||
| let todoResult = null; | ||
| for (const block of message.content) { | ||
| const blockType = block.type; | ||
| if (blockType === 'tool_use') { | ||
| const name = block.name; | ||
| const id = block.id; | ||
| if (name === 'Agent' || name === 'Task') { | ||
| // Agent spawn | ||
| const input = block.input; | ||
| const agentType = input?.subagent_type || 'Agent'; | ||
| const agentDesc = typeof input?.description === 'string' ? input.description : undefined; | ||
| agents.set(id, { | ||
| name: agentType, | ||
| model: input?.model, | ||
| status: 'running', | ||
| description: agentDesc, | ||
| }); | ||
| } | ||
| else if (name === 'Skill') { | ||
| // Track loaded skills | ||
| const input = block.input; | ||
| if (input?.skill && typeof input.skill === 'string') { | ||
| skills.add(input.skill); | ||
| } | ||
| } | ||
| else if (TODO_WRITE_NAME.test(name)) { | ||
| // Track todos | ||
| const input = block.input; | ||
| const todos = input?.todos; | ||
| if (Array.isArray(todos)) { | ||
| const total = todos.length; | ||
| const completed = todos.filter((t) => t.status === 'completed').length; | ||
| todoResult = { completed, total }; | ||
| } | ||
| } | ||
| else { | ||
| // Regular tool — extract file target for Read/Edit/Write | ||
| const input = block.input; | ||
| let target; | ||
| if (input?.file_path && typeof input.file_path === 'string') { | ||
| target = path.basename(input.file_path); | ||
| } | ||
| // Extract description: prefer input.description, fallback to first 4 words of command (Bash) | ||
| let description; | ||
| if (typeof input?.description === 'string') { | ||
| description = input.description; | ||
| } | ||
| else if (name === 'Bash' && typeof input?.command === 'string') { | ||
| description = input.command.split(/\s+/).slice(0, 4).join(' '); | ||
| } | ||
| tools.set(id, { name, status: 'running', target, description }); | ||
| } | ||
| } | ||
| else if (blockType === 'tool_result') { | ||
| const toolUseId = block.tool_use_id; | ||
| const toolEntry = tools.get(toolUseId); | ||
| if (toolEntry) { | ||
| toolEntry.status = 'completed'; | ||
| } | ||
| const agentEntry = agents.get(toolUseId); | ||
| if (agentEntry) { | ||
| agentEntry.status = 'completed'; | ||
| } | ||
| } | ||
| } | ||
| return todoResult; | ||
| } | ||
| //# sourceMappingURL=transcript.js.map |
| /** | ||
| * StdinData — the JSON that Claude Code pipes to statusLine commands. | ||
| */ | ||
| export interface StdinData { | ||
| model?: { | ||
| display_name?: string; | ||
| id?: string; | ||
| }; | ||
| cwd?: string; | ||
| context_window?: { | ||
| context_window_size?: number; | ||
| current_usage?: { | ||
| input_tokens?: number; | ||
| output_tokens?: number; | ||
| }; | ||
| used_percentage?: number; | ||
| }; | ||
| cost?: { | ||
| total_cost_usd?: number; | ||
| }; | ||
| session_id?: string; | ||
| transcript_path?: string; | ||
| } | ||
| /** | ||
| * Component IDs — the 14 HUD components. | ||
| */ | ||
| export type ComponentId = 'directory' | 'gitBranch' | 'gitAheadBehind' | 'diffStats' | 'model' | 'contextUsage' | 'versionBadge' | 'sessionDuration' | 'usageQuota' | 'todoProgress' | 'configCounts' | 'sessionCost' | 'releaseInfo' | 'worktreeCount'; | ||
| /** | ||
| * HUD config persisted to ~/.devflow/hud.json. | ||
| */ | ||
| export interface HudConfig { | ||
| enabled: boolean; | ||
| detail: boolean; | ||
| } | ||
| /** | ||
| * Component render result. | ||
| */ | ||
| export interface ComponentResult { | ||
| text: string; | ||
| raw: string; | ||
| } | ||
| /** | ||
| * Component function signature. | ||
| */ | ||
| export type ComponentFn = (ctx: GatherContext) => Promise<ComponentResult | null>; | ||
| /** | ||
| * Git status data gathered from the working directory. | ||
| */ | ||
| export interface GitStatus { | ||
| branch: string; | ||
| dirty: boolean; | ||
| staged: boolean; | ||
| ahead: number; | ||
| behind: number; | ||
| filesChanged: number; | ||
| additions: number; | ||
| deletions: number; | ||
| lastTag: string | null; | ||
| commitsSinceTag: number; | ||
| worktreeCount: number; | ||
| } | ||
| /** | ||
| * Transcript data parsed from session JSONL. | ||
| */ | ||
| export interface TranscriptData { | ||
| tools: Array<{ | ||
| name: string; | ||
| status: 'running' | 'completed'; | ||
| target?: string; | ||
| description?: string; | ||
| }>; | ||
| agents: Array<{ | ||
| name: string; | ||
| model?: string; | ||
| status: 'running' | 'completed'; | ||
| description?: string; | ||
| }>; | ||
| todos: { | ||
| completed: number; | ||
| total: number; | ||
| }; | ||
| skills: string[]; | ||
| } | ||
| /** | ||
| * Usage API data. | ||
| */ | ||
| export interface UsageData { | ||
| fiveHourPercent: number | null; | ||
| sevenDayPercent: number | null; | ||
| } | ||
| /** | ||
| * Config counts data for the configCounts component. | ||
| */ | ||
| export interface ConfigCountsData { | ||
| claudeMdFiles: number; | ||
| rules: number; | ||
| mcpServers: number; | ||
| hooks: number; | ||
| } | ||
| /** | ||
| * Gather context passed to all component render functions. | ||
| */ | ||
| export interface GatherContext { | ||
| stdin: StdinData; | ||
| git: GitStatus | null; | ||
| transcript: TranscriptData | null; | ||
| usage: UsageData | null; | ||
| configCounts: ConfigCountsData | null; | ||
| config: HudConfig & { | ||
| components: ComponentId[]; | ||
| }; | ||
| devflowDir: string; | ||
| sessionStartTime: number | null; | ||
| terminalWidth: number; | ||
| } | ||
| //# sourceMappingURL=types.d.ts.map |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| import type { UsageData } from './types.js'; | ||
| /** | ||
| * Fetch usage quota data from the Anthropic API. | ||
| * Uses caching with backoff for rate limiting. Returns null on failure. | ||
| */ | ||
| export declare function fetchUsageData(): Promise<UsageData | null>; | ||
| //# sourceMappingURL=usage-api.d.ts.map |
| import * as fs from 'node:fs'; | ||
| import { readCache, writeCache, readCacheStale } from './cache.js'; | ||
| import { getCredentials } from './credentials.js'; | ||
| const USAGE_CACHE_KEY = 'usage'; | ||
| const USAGE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes | ||
| const USAGE_FAIL_TTL = 15 * 1000; // 15 seconds | ||
| const API_TIMEOUT = 1_500; // Must fit within 2s overall HUD timeout | ||
| const BACKOFF_CACHE_KEY = 'usage-backoff'; | ||
| const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG; | ||
| function debugLog(msg, data) { | ||
| if (!DEBUG) | ||
| return; | ||
| const entry = { ts: new Date().toISOString(), source: 'usage-api', msg, ...data }; | ||
| fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n'); | ||
| } | ||
| /** | ||
| * Fetch usage quota data from the Anthropic API. | ||
| * Uses caching with backoff for rate limiting. Returns null on failure. | ||
| */ | ||
| export async function fetchUsageData() { | ||
| // Check backoff | ||
| const backoff = readCache(BACKOFF_CACHE_KEY); | ||
| if (backoff && Date.now() < backoff.retryAfter) { | ||
| debugLog('skipped: backoff active', { retryAfter: backoff.retryAfter }); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| // Check cache | ||
| const cached = readCache(USAGE_CACHE_KEY); | ||
| if (cached) | ||
| return cached; | ||
| const creds = await getCredentials(); | ||
| if (!creds) { | ||
| debugLog('no OAuth credentials found'); | ||
| return null; | ||
| } | ||
| const token = creds.accessToken; | ||
| try { | ||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), API_TIMEOUT); | ||
| debugLog('fetching usage', { timeout: API_TIMEOUT }); | ||
| const response = await fetch('https://api.anthropic.com/api/oauth/usage', { | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| 'Content-Type': 'application/json', | ||
| 'anthropic-beta': 'oauth-2025-04-20', | ||
| }, | ||
| signal: controller.signal, | ||
| }); | ||
| clearTimeout(timeout); | ||
| if (response.status === 429) { | ||
| const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10); | ||
| const delay = Math.min(retryAfter * 1000, 5 * 60 * 1000); | ||
| writeCache(BACKOFF_CACHE_KEY, { retryAfter: Date.now() + delay, delay }, delay); | ||
| debugLog('rate limited (429)', { retryAfter, delay }); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| if (!response.ok) { | ||
| debugLog('non-200 response', { status: response.status, statusText: response.statusText }); | ||
| writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| const body = (await response.json()); | ||
| const fiveHour = body.five_hour; | ||
| const sevenDay = body.seven_day; | ||
| const data = { | ||
| fiveHourPercent: typeof fiveHour?.utilization === 'number' | ||
| ? Math.round(Math.max(0, Math.min(100, fiveHour.utilization))) | ||
| : null, | ||
| sevenDayPercent: typeof sevenDay?.utilization === 'number' | ||
| ? Math.round(Math.max(0, Math.min(100, sevenDay.utilization))) | ||
| : null, | ||
| }; | ||
| debugLog('usage fetched', { fiveHour: data.fiveHourPercent, sevenDay: data.sevenDayPercent }); | ||
| writeCache(USAGE_CACHE_KEY, data, USAGE_CACHE_TTL); | ||
| return data; | ||
| } | ||
| catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| debugLog('fetch failed', { error: message }); | ||
| writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| } | ||
| //# sourceMappingURL=usage-api.js.map |
| # Stub Detection Patterns | ||
| Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues. | ||
| Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns. | ||
| --- | ||
| ## 1. Component Stubs | ||
| Render nothing meaningful or return placeholder markup. | ||
| ```tsx | ||
| // STUB — returns static text, no real rendering | ||
| function UserProfile({ userId }: Props) { | ||
| return <div>Name</div>; | ||
| } | ||
| // REAL — fetches and renders actual data | ||
| function UserProfile({ userId }: Props) { | ||
| const user = useUser(userId); | ||
| if (!user) return <Skeleton />; | ||
| return <div><h2>{user.name}</h2><p>{user.email}</p></div>; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `return null` / `return <></>` in components that should render content | ||
| - Empty function bodies (`{}`) for handlers or lifecycle methods | ||
| - Components returning only hardcoded strings with no data binding | ||
| --- | ||
| ## 2. API / Service Stubs | ||
| Functions that exist in signature but throw, return hardcoded values, or do nothing. | ||
| ```typescript | ||
| // STUB — throws instead of implementing | ||
| async function createOrder(items: CartItem[]): Promise<Order> { | ||
| throw new Error("TODO: implement"); | ||
| } | ||
| // STUB — hardcoded return, no real logic | ||
| async function getUser(id: string): Promise<User> { | ||
| return { id, name: "Test User", email: "test@test.com" }; | ||
| } | ||
| // REAL — actual implementation | ||
| async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> { | ||
| const validated = validateItems(items); | ||
| if (!validated.ok) return validated; | ||
| const order = await db.orders.create({ items: validated.value }); | ||
| await queue.publish("order.created", order); | ||
| return { ok: true, value: order }; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `throw new Error("TODO")` / `throw new Error("Not implemented")` | ||
| - Functions returning hardcoded objects (no DB/API/computation) | ||
| - `"Not implemented"` strings in response bodies | ||
| - Empty async functions (`async function foo() {}`) | ||
| --- | ||
| ## 3. Hook / Effect Stubs | ||
| State and effects declared but not wired to behavior. | ||
| ```typescript | ||
| // STUB — effect does nothing | ||
| useEffect(() => {}, [userId]); | ||
| // STUB — state declared, setter never called | ||
| const [items, setItems] = useState<Item[]>([]); | ||
| // ... setItems never appears in the component | ||
| // STUB — custom hook returns static value | ||
| function usePermissions(): Permissions { | ||
| return { canEdit: true, canDelete: false }; | ||
| } | ||
| // REAL — custom hook with actual logic | ||
| function usePermissions(): Permissions { | ||
| const { user } = useAuth(); | ||
| const { data } = useQuery(["permissions", user.role], fetchPermissions); | ||
| return data ?? DEFAULT_PERMISSIONS; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `useEffect(() => {}, [...])` — empty effect body | ||
| - `useState` where the setter is never called in the component | ||
| - Custom hooks returning static/hardcoded values | ||
| - `useMemo`/`useCallback` wrapping static values | ||
| --- | ||
| ## 4. Wiring Gaps | ||
| Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end. | ||
| ```typescript | ||
| // GAP — fetch without await (result discarded) | ||
| function loadDashboard() { | ||
| fetchMetrics(); // Promise floats, never awaited | ||
| return <Dashboard />; | ||
| } | ||
| // GAP — state declared but never rendered | ||
| const [error, setError] = useState<string | null>(null); | ||
| // ... error never appears in JSX | ||
| // GAP — handler defined but not bound | ||
| function handleSubmit(data: FormData) { /* real logic */ } | ||
| // ... <form onSubmit={handleSubmit}> never appears | ||
| // GAP — route defined with no-op handler | ||
| app.post("/api/orders", (_req, res) => { | ||
| res.status(200).json({ ok: true }); | ||
| }); | ||
| // GAP — env var read but unused | ||
| const API_KEY = process.env.STRIPE_API_KEY; | ||
| // ... API_KEY never passed to any client or fetch call | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `fetch`/`axios`/API call without `await` or `.then` (result discarded) | ||
| - State variable (`useState`, `useRef`) never rendered or read in output | ||
| - Event handler defined but not bound to any element/listener | ||
| - Route/endpoint registered with empty or no-op handler | ||
| - Environment variable or config read but never used downstream | ||
| - Import used only in type position but imported as value (in non-type-only import) |
| # Stub Detection Patterns | ||
| Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues. | ||
| Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns. | ||
| --- | ||
| ## 1. Component Stubs | ||
| Render nothing meaningful or return placeholder markup. | ||
| ```tsx | ||
| // STUB — returns static text, no real rendering | ||
| function UserProfile({ userId }: Props) { | ||
| return <div>Name</div>; | ||
| } | ||
| // REAL — fetches and renders actual data | ||
| function UserProfile({ userId }: Props) { | ||
| const user = useUser(userId); | ||
| if (!user) return <Skeleton />; | ||
| return <div><h2>{user.name}</h2><p>{user.email}</p></div>; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `return null` / `return <></>` in components that should render content | ||
| - Empty function bodies (`{}`) for handlers or lifecycle methods | ||
| - Components returning only hardcoded strings with no data binding | ||
| --- | ||
| ## 2. API / Service Stubs | ||
| Functions that exist in signature but throw, return hardcoded values, or do nothing. | ||
| ```typescript | ||
| // STUB — throws instead of implementing | ||
| async function createOrder(items: CartItem[]): Promise<Order> { | ||
| throw new Error("TODO: implement"); | ||
| } | ||
| // STUB — hardcoded return, no real logic | ||
| async function getUser(id: string): Promise<User> { | ||
| return { id, name: "Test User", email: "test@test.com" }; | ||
| } | ||
| // REAL — actual implementation | ||
| async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> { | ||
| const validated = validateItems(items); | ||
| if (!validated.ok) return validated; | ||
| const order = await db.orders.create({ items: validated.value }); | ||
| await queue.publish("order.created", order); | ||
| return { ok: true, value: order }; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `throw new Error("TODO")` / `throw new Error("Not implemented")` | ||
| - Functions returning hardcoded objects (no DB/API/computation) | ||
| - `"Not implemented"` strings in response bodies | ||
| - Empty async functions (`async function foo() {}`) | ||
| --- | ||
| ## 3. Hook / Effect Stubs | ||
| State and effects declared but not wired to behavior. | ||
| ```typescript | ||
| // STUB — effect does nothing | ||
| useEffect(() => {}, [userId]); | ||
| // STUB — state declared, setter never called | ||
| const [items, setItems] = useState<Item[]>([]); | ||
| // ... setItems never appears in the component | ||
| // STUB — custom hook returns static value | ||
| function usePermissions(): Permissions { | ||
| return { canEdit: true, canDelete: false }; | ||
| } | ||
| // REAL — custom hook with actual logic | ||
| function usePermissions(): Permissions { | ||
| const { user } = useAuth(); | ||
| const { data } = useQuery(["permissions", user.role], fetchPermissions); | ||
| return data ?? DEFAULT_PERMISSIONS; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `useEffect(() => {}, [...])` — empty effect body | ||
| - `useState` where the setter is never called in the component | ||
| - Custom hooks returning static/hardcoded values | ||
| - `useMemo`/`useCallback` wrapping static values | ||
| --- | ||
| ## 4. Wiring Gaps | ||
| Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end. | ||
| ```typescript | ||
| // GAP — fetch without await (result discarded) | ||
| function loadDashboard() { | ||
| fetchMetrics(); // Promise floats, never awaited | ||
| return <Dashboard />; | ||
| } | ||
| // GAP — state declared but never rendered | ||
| const [error, setError] = useState<string | null>(null); | ||
| // ... error never appears in JSX | ||
| // GAP — handler defined but not bound | ||
| function handleSubmit(data: FormData) { /* real logic */ } | ||
| // ... <form onSubmit={handleSubmit}> never appears | ||
| // GAP — route defined with no-op handler | ||
| app.post("/api/orders", (_req, res) => { | ||
| res.status(200).json({ ok: true }); | ||
| }); | ||
| // GAP — env var read but unused | ||
| const API_KEY = process.env.STRIPE_API_KEY; | ||
| // ... API_KEY never passed to any client or fetch call | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `fetch`/`axios`/API call without `await` or `.then` (result discarded) | ||
| - State variable (`useState`, `useRef`) never rendered or read in output | ||
| - Event handler defined but not bound to any element/listener | ||
| - Route/endpoint registered with empty or no-op handler | ||
| - Environment variable or config read but never used downstream | ||
| - Import used only in type position but imported as value (in non-type-only import) |
| #!/usr/bin/env bash | ||
| # DevFlow HUD — configurable TypeScript status line | ||
| # Receives JSON via stdin from Claude Code, outputs ANSI-formatted HUD | ||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| exec node "${SCRIPT_DIR}/hud/index.js" |
| export declare function getCacheDir(): string; | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| */ | ||
| export declare function readCache<T>(key: string): T | null; | ||
| /** | ||
| * Read a cached value regardless of TTL (stale data). Returns null if missing. | ||
| */ | ||
| export declare function readCacheStale<T>(key: string): T | null; | ||
| /** | ||
| * Write a value to cache with a TTL in milliseconds. | ||
| */ | ||
| export declare function writeCache<T>(key: string, data: T, ttlMs: number): void; | ||
| //# sourceMappingURL=cache.d.ts.map |
| {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cli/hud/cache.ts"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAoBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAElD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEvD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAWvE"} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| export function getCacheDir() { | ||
| const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow'); | ||
| return path.join(devflowDir, 'cache'); | ||
| } | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| * When `ignoreExpiry` is true, returns data regardless of TTL (stale read). | ||
| */ | ||
| function readCacheEntry(key, ignoreExpiry) { | ||
| try { | ||
| const filePath = path.join(getCacheDir(), `${key}.json`); | ||
| const raw = fs.readFileSync(filePath, 'utf-8'); | ||
| const entry = JSON.parse(raw); | ||
| if (ignoreExpiry || Date.now() - entry.timestamp < entry.ttl) { | ||
| return entry.data; | ||
| } | ||
| return null; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| /** | ||
| * Read a cached value. Returns null if missing or expired. | ||
| */ | ||
| export function readCache(key) { | ||
| return readCacheEntry(key, false); | ||
| } | ||
| /** | ||
| * Read a cached value regardless of TTL (stale data). Returns null if missing. | ||
| */ | ||
| export function readCacheStale(key) { | ||
| return readCacheEntry(key, true); | ||
| } | ||
| /** | ||
| * Write a value to cache with a TTL in milliseconds. | ||
| */ | ||
| export function writeCache(key, data, ttlMs) { | ||
| try { | ||
| const dir = getCacheDir(); | ||
| if (!fs.existsSync(dir)) { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } | ||
| const entry = { data, timestamp: Date.now(), ttl: ttlMs }; | ||
| fs.writeFileSync(path.join(dir, `${key}.json`), JSON.stringify(entry)); | ||
| } | ||
| catch { | ||
| // Cache write failure is non-fatal | ||
| } | ||
| } | ||
| //# sourceMappingURL=cache.js.map |
| {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cli/hud/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAQlC,MAAM,UAAU,WAAW;IACzB,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAI,GAAW,EAAE,YAAqB;IAC3D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAC/C,IAAI,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;YAC7D,OAAO,KAAK,CAAC,IAAI,CAAC;QACpB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAI,GAAW;IACtC,OAAO,cAAc,CAAI,GAAG,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAI,GAAW;IAC3C,OAAO,cAAc,CAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAI,GAAW,EAAE,IAAO,EAAE,KAAa;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QACD,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QACzE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC"} |
| /** | ||
| * ANSI color helpers — no dependencies, precompiled escape sequences. | ||
| * Used by HUD components for direct terminal output (not @clack/prompts). | ||
| */ | ||
| export declare function bold(s: string): string; | ||
| export declare function dim(s: string): string; | ||
| export declare function red(s: string): string; | ||
| export declare function green(s: string): string; | ||
| export declare function yellow(s: string): string; | ||
| export declare function blue(s: string): string; | ||
| export declare function magenta(s: string): string; | ||
| export declare function cyan(s: string): string; | ||
| export declare function gray(s: string): string; | ||
| export declare function white(s: string): string; | ||
| export declare function orange(s: string): string; | ||
| export declare function brightRed(s: string): string; | ||
| export declare function boldRed(s: string): string; | ||
| export declare function bgGreen(s: string): string; | ||
| export declare function bgYellow(s: string): string; | ||
| export declare function bgRed(s: string): string; | ||
| export declare function truncate(s: string, max: number): string; | ||
| export declare function stripAnsi(s: string): string; | ||
| //# sourceMappingURL=colors.d.ts.map |
| {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/cli/hud/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErC;AACD,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErC;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAID,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C"} |
| /** | ||
| * ANSI color helpers — no dependencies, precompiled escape sequences. | ||
| * Used by HUD components for direct terminal output (not @clack/prompts). | ||
| */ | ||
| const ESC = '\x1b['; | ||
| const RESET = `${ESC}0m`; | ||
| export function bold(s) { | ||
| return `${ESC}1m${s}${RESET}`; | ||
| } | ||
| export function dim(s) { | ||
| return `${ESC}2m${s}${RESET}`; | ||
| } | ||
| export function red(s) { | ||
| return `${ESC}31m${s}${RESET}`; | ||
| } | ||
| export function green(s) { | ||
| return `${ESC}32m${s}${RESET}`; | ||
| } | ||
| export function yellow(s) { | ||
| return `${ESC}33m${s}${RESET}`; | ||
| } | ||
| export function blue(s) { | ||
| return `${ESC}34m${s}${RESET}`; | ||
| } | ||
| export function magenta(s) { | ||
| return `${ESC}35m${s}${RESET}`; | ||
| } | ||
| export function cyan(s) { | ||
| return `${ESC}36m${s}${RESET}`; | ||
| } | ||
| export function gray(s) { | ||
| return `${ESC}90m${s}${RESET}`; | ||
| } | ||
| export function white(s) { | ||
| return `${ESC}37m${s}${RESET}`; | ||
| } | ||
| export function orange(s) { | ||
| return `${ESC}38;5;208m${s}${RESET}`; | ||
| } | ||
| export function brightRed(s) { | ||
| return `${ESC}91m${s}${RESET}`; | ||
| } | ||
| export function boldRed(s) { | ||
| return `${ESC}1;31m${s}${RESET}`; | ||
| } | ||
| export function bgGreen(s) { | ||
| return `${ESC}42m${s}${RESET}`; | ||
| } | ||
| export function bgYellow(s) { | ||
| return `${ESC}43m${s}${RESET}`; | ||
| } | ||
| export function bgRed(s) { | ||
| return `${ESC}41m${s}${RESET}`; | ||
| } | ||
| export function truncate(s, max) { | ||
| return s.length > max ? s.slice(0, max - 1) + '\u2026' : s; | ||
| } | ||
| const ANSI_PATTERN = /\x1b\[[0-9;]*m/g; | ||
| export function stripAnsi(s) { | ||
| return s.replace(ANSI_PATTERN, ''); | ||
| } | ||
| //# sourceMappingURL=colors.js.map |
| {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/cli/hud/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC;AAEzB,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AAChC,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AAChC,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,GAAG,GAAG,YAAY,CAAC,GAAG,KAAK,EAAE,CAAC;AACvC,CAAC;AACD,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,QAAQ,CAAC,GAAG,KAAK,EAAE,CAAC;AACnC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,GAAW;IAC7C,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC"} |
| import type { ComponentResult, GatherContext, ConfigCountsData } from '../types.js'; | ||
| /** | ||
| * Gather configuration counts for the configCounts component. | ||
| * Exported for use by the main HUD entry point. | ||
| */ | ||
| export declare function gatherConfigCounts(cwd: string): ConfigCountsData; | ||
| export default function configCounts(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=config-counts.d.ts.map |
| {"version":3,"file":"config-counts.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/config-counts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqCpF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAoChE;AAED,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAejC"} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { dim } from '../colors.js'; | ||
| function countClaudeMdFiles(cwd) { | ||
| let count = 0; | ||
| // Check project CLAUDE.md | ||
| if (fs.existsSync(path.join(cwd, 'CLAUDE.md'))) | ||
| count++; | ||
| // Check user CLAUDE.md | ||
| const claudeDir = process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude'); | ||
| if (fs.existsSync(path.join(claudeDir, 'CLAUDE.md'))) | ||
| count++; | ||
| return count; | ||
| } | ||
| function countFromSettings(settingsPath) { | ||
| try { | ||
| const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); | ||
| const mcpServers = settings.mcpServers | ||
| ? Object.keys(settings.mcpServers).length | ||
| : 0; | ||
| let hooks = 0; | ||
| if (settings.hooks) { | ||
| const hooksObj = settings.hooks; | ||
| for (const event of Object.values(hooksObj)) { | ||
| if (Array.isArray(event)) | ||
| hooks += event.length; | ||
| } | ||
| } | ||
| return { mcpServers, hooks }; | ||
| } | ||
| catch { | ||
| return { mcpServers: 0, hooks: 0 }; | ||
| } | ||
| } | ||
| /** | ||
| * Gather configuration counts for the configCounts component. | ||
| * Exported for use by the main HUD entry point. | ||
| */ | ||
| export function gatherConfigCounts(cwd) { | ||
| const claudeDir = process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude'); | ||
| const claudeMdFiles = countClaudeMdFiles(cwd); | ||
| // Count rules (.md/.mdc files in .claude/rules) | ||
| let rules = 0; | ||
| for (const rulesDir of [ | ||
| path.join(cwd, '.claude', 'rules'), | ||
| path.join(claudeDir, 'rules'), | ||
| ]) { | ||
| try { | ||
| const files = fs.readdirSync(rulesDir); | ||
| rules += files.filter((f) => f.endsWith('.md') || f.endsWith('.mdc')).length; | ||
| } | ||
| catch { | ||
| /* ignore */ | ||
| } | ||
| } | ||
| // Aggregate settings from user and project | ||
| const userSettings = countFromSettings(path.join(claudeDir, 'settings.json')); | ||
| const projectSettings = countFromSettings(path.join(cwd, '.claude', 'settings.json')); | ||
| return { | ||
| claudeMdFiles, | ||
| rules, | ||
| mcpServers: userSettings.mcpServers + projectSettings.mcpServers, | ||
| hooks: userSettings.hooks + projectSettings.hooks, | ||
| }; | ||
| } | ||
| export default async function configCounts(ctx) { | ||
| if (!ctx.configCounts) | ||
| return null; | ||
| const { claudeMdFiles, rules: ruleCount, mcpServers, hooks: hookCount } = ctx.configCounts; | ||
| const skillCount = ctx.transcript?.skills.length ?? 0; | ||
| const parts = []; | ||
| if (claudeMdFiles > 0) | ||
| parts.push(`${claudeMdFiles} CLAUDE.md`); | ||
| if (ruleCount > 0) | ||
| parts.push(`${ruleCount} rules`); | ||
| if (mcpServers > 0) | ||
| parts.push(`${mcpServers} MCPs`); | ||
| if (hookCount > 0) | ||
| parts.push(`${hookCount} hooks`); | ||
| if (skillCount > 0) | ||
| parts.push(`${skillCount} skills`); | ||
| if (parts.length === 0) | ||
| return null; | ||
| const raw = parts.join(' \u00B7 '); | ||
| const text = parts.map((p) => dim(p)).join(dim(' \u00B7 ')); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=config-counts.js.map |
| {"version":3,"file":"config-counts.js","sourceRoot":"","sources":["../../../src/cli/hud/components/config-counts.ts"],"names":[],"mappings":"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"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| /** | ||
| * Context window usage component. | ||
| * Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red. | ||
| * At >85% appends token breakdown: (in: Nk). | ||
| */ | ||
| export default function contextUsage(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=context-usage.d.ts.map |
| {"version":3,"file":"context-usage.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/context-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE;;;;GAIG;AACH,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4CjC"} |
| import { dim, green, yellow, red } from '../colors.js'; | ||
| const BAR_WIDTH = 8; | ||
| /** | ||
| * Context window usage component. | ||
| * Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red. | ||
| * At >85% appends token breakdown: (in: Nk). | ||
| */ | ||
| export default async function contextUsage(ctx) { | ||
| const cw = ctx.stdin.context_window; | ||
| if (!cw) | ||
| return null; | ||
| let pct = null; | ||
| if (cw.used_percentage !== undefined) { | ||
| pct = Math.round(cw.used_percentage); | ||
| } | ||
| else if (cw.context_window_size && cw.current_usage?.input_tokens !== undefined) { | ||
| pct = Math.round((cw.current_usage.input_tokens / cw.context_window_size) * 100); | ||
| } | ||
| if (pct === null) | ||
| return null; | ||
| const filled = Math.round((pct / 100) * BAR_WIDTH); | ||
| const empty = BAR_WIDTH - filled; | ||
| let colorFn; | ||
| if (pct < 50) { | ||
| colorFn = green; | ||
| } | ||
| else if (pct < 80) { | ||
| colorFn = yellow; | ||
| } | ||
| else { | ||
| colorFn = red; | ||
| } | ||
| const filledBar = '\u2588'.repeat(filled); | ||
| const emptyBar = '\u2591'.repeat(empty); | ||
| let suffix = ''; | ||
| if (pct > 85 && cw.current_usage?.input_tokens !== undefined) { | ||
| const inK = Math.round(cw.current_usage.input_tokens / 1000); | ||
| suffix = ` (in: ${inK}k)`; | ||
| } | ||
| const raw = `Current Session ${filledBar}${emptyBar} ${pct}%${suffix}`; | ||
| const text = dim('Current Session ') + | ||
| colorFn(filledBar) + | ||
| dim(emptyBar) + | ||
| ' ' + | ||
| colorFn(`${pct}%`) + | ||
| (suffix ? dim(suffix) : ''); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=context-usage.js.map |
| {"version":3,"file":"context-usage.js","sourceRoot":"","sources":["../../../src/cli/hud/components/context-usage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;;;;GAIG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,YAAY,CACxC,GAAkB;IAElB,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC;IACpC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAErB,IAAI,GAAG,GAAkB,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACrC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,EAAE,CAAC,mBAAmB,IAAI,EAAE,CAAC,aAAa,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;QAClF,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,YAAY,GAAG,EAAE,CAAC,mBAAmB,CAAC,GAAG,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAEjC,IAAI,OAA8B,CAAC;IACnC,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;QACb,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;SAAM,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;QACpB,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,aAAa,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QAC7D,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,GAAG,GAAG,mBAAmB,SAAS,GAAG,QAAQ,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;IACvE,MAAM,IAAI,GACR,GAAG,CAAC,kBAAkB,CAAC;QACvB,OAAO,CAAC,SAAS,CAAC;QAClB,GAAG,CAAC,QAAQ,CAAC;QACb,GAAG;QACH,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;QAClB,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE9B,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function diffStats(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=diff-stats.d.ts.map |
| {"version":3,"file":"diff-stats.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/diff-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA+BjC"} |
| import { dim, green, red } from '../colors.js'; | ||
| export default async function diffStats(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const { filesChanged, additions, deletions } = ctx.git; | ||
| if (filesChanged === 0 && additions === 0 && deletions === 0) | ||
| return null; | ||
| const filePart = filesChanged > 0 | ||
| ? `${filesChanged} file${filesChanged === 1 ? '' : 's'}` | ||
| : ''; | ||
| const lineParts = []; | ||
| if (additions > 0) | ||
| lineParts.push(`+${additions}`); | ||
| if (deletions > 0) | ||
| lineParts.push(`-${deletions}`); | ||
| const sections = []; | ||
| const rawSections = []; | ||
| if (filePart) { | ||
| sections.push(dim(filePart)); | ||
| rawSections.push(filePart); | ||
| } | ||
| if (lineParts.length > 0) { | ||
| const lineText = lineParts | ||
| .map((p) => (p.startsWith('+') ? green(p) : red(p))) | ||
| .join(' '); | ||
| sections.push(lineText); | ||
| rawSections.push(lineParts.join(' ')); | ||
| } | ||
| if (sections.length === 0) | ||
| return null; | ||
| return { | ||
| text: sections.join(dim(' \u00B7 ')), | ||
| raw: rawSections.join(' \u00B7 '), | ||
| }; | ||
| } | ||
| //# sourceMappingURL=diff-stats.js.map |
| {"version":3,"file":"diff-stats.js","sourceRoot":"","sources":["../../../src/cli/hud/components/diff-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IACvD,IAAI,YAAY,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,MAAM,QAAQ,GAAG,YAAY,GAAG,CAAC;QAC/B,CAAC,CAAC,GAAG,YAAY,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QACxD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,SAAS,GAAG,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IACnD,IAAI,SAAS,GAAG,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7B,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,SAAS;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aACnD,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACpC,GAAG,EAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;KAClC,CAAC;AACJ,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function directory(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=directory.d.ts.map |
| {"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/directory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"} |
| import { bold, white } from '../colors.js'; | ||
| import * as path from 'node:path'; | ||
| export default async function directory(ctx) { | ||
| const cwd = ctx.stdin.cwd; | ||
| if (!cwd) | ||
| return null; | ||
| const name = path.basename(cwd); | ||
| return { text: bold(white(name)), raw: name }; | ||
| } | ||
| //# sourceMappingURL=directory.js.map |
| {"version":3,"file":"directory.js","sourceRoot":"","sources":["../../../src/cli/hud/components/directory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAChD,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function gitAheadBehind(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=git-ahead-behind.d.ts.map |
| {"version":3,"file":"git-ahead-behind.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/git-ahead-behind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,cAAc,CAC1C,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CASjC"} |
| import { dim } from '../colors.js'; | ||
| export default async function gitAheadBehind(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const { ahead, behind } = ctx.git; | ||
| if (ahead === 0 && behind === 0) | ||
| return null; | ||
| const rawParts = []; | ||
| if (ahead > 0) | ||
| rawParts.push(`${ahead}\u2191`); | ||
| if (behind > 0) | ||
| rawParts.push(`${behind}\u2193`); | ||
| const raw = rawParts.join(' '); | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=git-ahead-behind.js.map |
| {"version":3,"file":"git-ahead-behind.js","sourceRoot":"","sources":["../../../src/cli/hud/components/git-ahead-behind.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,cAAc,CAC1C,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAClC,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC;IAC/C,IAAI,MAAM,GAAG,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function gitBranch(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=git-branch.d.ts.map |
| {"version":3,"file":"git-branch.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/git-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAWjC"} |
| import { white, yellow, green } from '../colors.js'; | ||
| export default async function gitBranch(ctx) { | ||
| if (!ctx.git) | ||
| return null; | ||
| const dirtyMark = ctx.git.dirty ? '*' : ''; | ||
| const stagedMark = ctx.git.staged ? '+' : ''; | ||
| const indicator = dirtyMark + stagedMark; | ||
| const text = white(ctx.git.branch) + | ||
| (dirtyMark ? yellow(dirtyMark) : '') + | ||
| (stagedMark ? green(stagedMark) : ''); | ||
| const raw = ctx.git.branch + indicator; | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=git-branch.js.map |
| {"version":3,"file":"git-branch.js","sourceRoot":"","sources":["../../../src/cli/hud/components/git-branch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACzC,MAAM,IAAI,GACR,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;QACrB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;IACvC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function model(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=model.d.ts.map |
| {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,KAAK,CACjC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmBjC"} |
| import { dim, white } from '../colors.js'; | ||
| export default async function model(ctx) { | ||
| const name = ctx.stdin.model?.display_name; | ||
| if (!name) | ||
| return null; | ||
| // Strip "Claude " prefix and trailing context info like "(1M context)" for brevity | ||
| const short = name | ||
| .replace(/^Claude\s+/i, '') | ||
| .replace(/\s*\(\d+[KkMm]\s*context\)\s*$/, ''); | ||
| const cwSize = ctx.stdin.context_window?.context_window_size; | ||
| let sizeStr = ''; | ||
| if (cwSize) { | ||
| sizeStr = | ||
| cwSize >= 1_000_000 | ||
| ? ` [${Math.round(cwSize / 1_000_000)}m]` | ||
| : ` [${Math.round(cwSize / 1000)}k]`; | ||
| } | ||
| const raw = short + sizeStr; | ||
| return { text: white(short) + (sizeStr ? dim(sizeStr) : ''), raw }; | ||
| } | ||
| //# sourceMappingURL=model.js.map |
| {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/cli/hud/components/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK,CACjC,GAAkB;IAElB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,mFAAmF;IACnF,MAAM,KAAK,GAAG,IAAI;SACf,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7D,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,MAAM,IAAI,SAAS;gBACjB,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,IAAI;gBACzC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;IAC3C,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,GAAG,OAAO,CAAC;IAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;AACrE,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function releaseInfo(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=release-info.d.ts.map |
| {"version":3,"file":"release-info.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/release-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,WAAW,CACvC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"} |
| import { dim } from '../colors.js'; | ||
| export default async function releaseInfo(ctx) { | ||
| if (!ctx.git?.lastTag) | ||
| return null; | ||
| const { lastTag, commitsSinceTag } = ctx.git; | ||
| const raw = commitsSinceTag > 0 ? `${lastTag} +${commitsSinceTag}` : lastTag; | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=release-info.js.map |
| {"version":3,"file":"release-info.js","sourceRoot":"","sources":["../../../src/cli/hud/components/release-info.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACvC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAC7C,MAAM,GAAG,GAAG,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,eAAe,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7E,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function sessionCost(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=session-cost.d.ts.map |
| {"version":3,"file":"session-cost.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/session-cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,WAAW,CACvC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"} |
| import { dim } from '../colors.js'; | ||
| export default async function sessionCost(ctx) { | ||
| const cost = ctx.stdin.cost?.total_cost_usd; | ||
| if (cost == null) | ||
| return null; | ||
| const formatted = `$${cost.toFixed(2)}`; | ||
| return { text: dim(formatted), raw: formatted }; | ||
| } | ||
| //# sourceMappingURL=session-cost.js.map |
| {"version":3,"file":"session-cost.js","sourceRoot":"","sources":["../../../src/cli/hud/components/session-cost.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACvC,GAAkB;IAElB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC;IAC5C,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AAClD,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function sessionDuration(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=session-duration.d.ts.map |
| {"version":3,"file":"session-duration.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/session-duration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,eAAe,CAC3C,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAajC"} |
| import { dim } from '../colors.js'; | ||
| export default async function sessionDuration(ctx) { | ||
| if (!ctx.sessionStartTime) | ||
| return null; | ||
| const elapsed = Math.floor((Date.now() - ctx.sessionStartTime) / 1000); | ||
| const minutes = Math.floor(elapsed / 60); | ||
| const hours = Math.floor(minutes / 60); | ||
| let label; | ||
| if (hours > 0) { | ||
| label = `${hours}h ${minutes % 60}m`; | ||
| } | ||
| else { | ||
| label = `${minutes}m`; | ||
| } | ||
| const text = `\u23F1 ${label}`; | ||
| return { text: dim(text), raw: text }; | ||
| } | ||
| //# sourceMappingURL=session-duration.js.map |
| {"version":3,"file":"session-duration.js","sourceRoot":"","sources":["../../../src/cli/hud/components/session-duration.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,eAAe,CAC3C,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAa,CAAC;IAClB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,KAAK,GAAG,GAAG,KAAK,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,GAAG,OAAO,GAAG,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,KAAK,EAAE,CAAC;IAC/B,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACxC,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function todoProgress(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=todo-progress.d.ts.map |
| {"version":3,"file":"todo-progress.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/todo-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAMjC"} |
| import { dim } from '../colors.js'; | ||
| export default async function todoProgress(ctx) { | ||
| if (!ctx.transcript) | ||
| return null; | ||
| const { todos } = ctx.transcript; | ||
| if (todos.total === 0) | ||
| return null; | ||
| const label = `${todos.completed}/${todos.total} todos`; | ||
| return { text: dim(label), raw: label }; | ||
| } | ||
| //# sourceMappingURL=todo-progress.js.map |
| {"version":3,"file":"todo-progress.js","sourceRoot":"","sources":["../../../src/cli/hud/components/todo-progress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,YAAY,CACxC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;IACjC,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,QAAQ,CAAC;IACxD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function usageQuota(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=usage-quota.d.ts.map |
| {"version":3,"file":"usage-quota.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/usage-quota.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA6BlE,wBAA8B,UAAU,CACtC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAqBjC"} |
| import { green, yellow, red, dim } from '../colors.js'; | ||
| const BAR_WIDTH = 8; | ||
| function renderBar(percent) { | ||
| const filled = Math.round((percent / 100) * BAR_WIDTH); | ||
| const empty = BAR_WIDTH - filled; | ||
| let colorFn; | ||
| if (percent < 50) { | ||
| colorFn = green; | ||
| } | ||
| else if (percent < 80) { | ||
| colorFn = yellow; | ||
| } | ||
| else { | ||
| colorFn = red; | ||
| } | ||
| const filledBar = '\u2588'.repeat(filled); | ||
| const emptyBar = '\u2591'.repeat(empty); | ||
| const text = colorFn(filledBar) + | ||
| dim(emptyBar) + | ||
| ' ' + | ||
| colorFn(`${percent}%`); | ||
| const raw = `${filledBar}${emptyBar} ${percent}%`; | ||
| return { text, raw }; | ||
| } | ||
| export default async function usageQuota(ctx) { | ||
| if (!ctx.usage) | ||
| return null; | ||
| const { fiveHourPercent, sevenDayPercent } = ctx.usage; | ||
| const parts = []; | ||
| if (fiveHourPercent !== null) { | ||
| const bar = renderBar(Math.round(fiveHourPercent)); | ||
| parts.push({ text: dim('5h ') + bar.text, raw: `5h ${bar.raw}` }); | ||
| } | ||
| if (sevenDayPercent !== null) { | ||
| const bar = renderBar(Math.round(sevenDayPercent)); | ||
| parts.push({ text: dim('7d ') + bar.text, raw: `7d ${bar.raw}` }); | ||
| } | ||
| if (parts.length === 0) | ||
| return null; | ||
| const sep = dim(' \u00B7 '); | ||
| const text = dim('Session ') + parts.map((p) => p.text).join(sep); | ||
| const raw = 'Session ' + parts.map((p) => p.raw).join(' \u00B7 '); | ||
| return { text, raw }; | ||
| } | ||
| //# sourceMappingURL=usage-quota.js.map |
| {"version":3,"file":"usage-quota.js","sourceRoot":"","sources":["../../../src/cli/hud/components/usage-quota.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAEjC,IAAI,OAA8B,CAAC;IACnC,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;SAAM,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACxB,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,IAAI,GACR,OAAO,CAAC,SAAS,CAAC;QAClB,GAAG,CAAC,QAAQ,CAAC;QACb,GAAG;QACH,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,GAAG,SAAS,GAAG,QAAQ,IAAI,OAAO,GAAG,CAAC;IAClD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,UAAU,CACtC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IACvD,MAAM,KAAK,GAAoC,EAAE,CAAC;IAElD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function versionBadge(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=version-badge.d.ts.map |
| {"version":3,"file":"version-badge.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/version-badge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyElE,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAqBjC"} |
| import { execFile } from 'node:child_process'; | ||
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { yellow } from '../colors.js'; | ||
| import { readCache, writeCache } from '../cache.js'; | ||
| const VERSION_CACHE_KEY = 'version-check'; | ||
| const VERSION_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours | ||
| function getCurrentVersion(devflowDir) { | ||
| // Try manifest.json first (most reliable for installed version) | ||
| try { | ||
| const manifestPath = path.join(devflowDir, 'manifest.json'); | ||
| const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); | ||
| if (typeof manifest.version === 'string') | ||
| return manifest.version; | ||
| } | ||
| catch { | ||
| // Fall through | ||
| } | ||
| // Try package.json as fallback | ||
| try { | ||
| const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'package.json'); | ||
| const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); | ||
| if (typeof pkg.version === 'string') | ||
| return pkg.version; | ||
| } | ||
| catch { | ||
| // Fall through | ||
| } | ||
| return null; | ||
| } | ||
| function fetchLatestVersion() { | ||
| return new Promise((resolve) => { | ||
| execFile('npm', ['view', 'devflow-kit', 'version', '--json'], { timeout: 5000 }, (err, stdout) => { | ||
| if (err) { | ||
| resolve(null); | ||
| return; | ||
| } | ||
| try { | ||
| const parsed = JSON.parse(stdout.trim()); | ||
| resolve(typeof parsed === 'string' ? parsed : null); | ||
| } | ||
| catch { | ||
| const trimmed = stdout.trim(); | ||
| resolve(trimmed || null); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| function compareVersions(current, latest) { | ||
| const a = current.split('.').map(Number); | ||
| const b = latest.split('.').map(Number); | ||
| for (let i = 0; i < 3; i++) { | ||
| if ((a[i] || 0) < (b[i] || 0)) | ||
| return -1; | ||
| if ((a[i] || 0) > (b[i] || 0)) | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } | ||
| export default async function versionBadge(ctx) { | ||
| const current = getCurrentVersion(ctx.devflowDir); | ||
| if (!current) | ||
| return null; | ||
| // Check cache | ||
| let info = readCache(VERSION_CACHE_KEY); | ||
| if (!info) { | ||
| const latest = await fetchLatestVersion(); | ||
| if (latest) { | ||
| info = { current, latest }; | ||
| writeCache(VERSION_CACHE_KEY, info, VERSION_CACHE_TTL); | ||
| } | ||
| } | ||
| if (info && compareVersions(info.current, info.latest) < 0) { | ||
| const badge = `\u2726 Devflow v${info.latest} \u00B7 update: npx devflow-kit init`; | ||
| return { text: yellow(badge), raw: badge }; | ||
| } | ||
| // Don't show version when up to date | ||
| return null; | ||
| } | ||
| //# sourceMappingURL=version-badge.js.map |
| {"version":3,"file":"version-badge.js","sourceRoot":"","sources":["../../../src/cli/hud/components/version-badge.ts"],"names":[],"mappings":"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"} |
| import type { ComponentResult, GatherContext } from '../types.js'; | ||
| export default function worktreeCount(ctx: GatherContext): Promise<ComponentResult | null>; | ||
| //# sourceMappingURL=worktree-count.d.ts.map |
| {"version":3,"file":"worktree-count.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/worktree-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,aAAa,CACzC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAIjC"} |
| import { dim } from '../colors.js'; | ||
| export default async function worktreeCount(ctx) { | ||
| if (!ctx.git || ctx.git.worktreeCount <= 1) | ||
| return null; | ||
| const raw = `${ctx.git.worktreeCount} worktrees`; | ||
| return { text: dim(raw), raw }; | ||
| } | ||
| //# sourceMappingURL=worktree-count.js.map |
| {"version":3,"file":"worktree-count.js","sourceRoot":"","sources":["../../../src/cli/hud/components/worktree-count.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CACzC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,YAAY,CAAC;IACjD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"} |
| import type { HudConfig, ComponentId } from './types.js'; | ||
| /** | ||
| * All 14 HUD components in display order. | ||
| */ | ||
| export declare const HUD_COMPONENTS: readonly ComponentId[]; | ||
| export declare function getConfigPath(): string; | ||
| export declare function loadConfig(): HudConfig; | ||
| export declare function saveConfig(config: HudConfig): void; | ||
| export declare function resolveComponents(config: HudConfig): ComponentId[]; | ||
| //# sourceMappingURL=config.d.ts.map |
| {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/hud/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,WAAW,EAehD,CAAC;AAEF,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,wBAAgB,UAAU,IAAI,SAAS,CAYtC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAOlD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,EAAE,CAIlE"} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| /** | ||
| * All 14 HUD components in display order. | ||
| */ | ||
| export const HUD_COMPONENTS = [ | ||
| 'directory', | ||
| 'gitBranch', | ||
| 'gitAheadBehind', | ||
| 'diffStats', | ||
| 'releaseInfo', | ||
| 'worktreeCount', | ||
| 'model', | ||
| 'contextUsage', | ||
| 'versionBadge', | ||
| 'sessionDuration', | ||
| 'sessionCost', | ||
| 'usageQuota', | ||
| 'todoProgress', | ||
| 'configCounts', | ||
| ]; | ||
| export function getConfigPath() { | ||
| const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow'); | ||
| return path.join(devflowDir, 'hud.json'); | ||
| } | ||
| export function loadConfig() { | ||
| const configPath = getConfigPath(); | ||
| try { | ||
| const raw = fs.readFileSync(configPath, 'utf-8'); | ||
| const parsed = JSON.parse(raw); | ||
| return { | ||
| enabled: parsed.enabled !== false, | ||
| detail: parsed.detail === true, | ||
| }; | ||
| } | ||
| catch { | ||
| return { enabled: true, detail: false }; | ||
| } | ||
| } | ||
| export function saveConfig(config) { | ||
| const configPath = getConfigPath(); | ||
| const dir = path.dirname(configPath); | ||
| if (!fs.existsSync(dir)) { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } | ||
| fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n'); | ||
| } | ||
| export function resolveComponents(config) { | ||
| if (config.enabled) | ||
| return [...HUD_COMPONENTS]; | ||
| // Version badge always renders so users see upgrade notifications | ||
| return ['versionBadge']; | ||
| } | ||
| //# sourceMappingURL=config.js.map |
| {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/hud/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,WAAW;IACX,WAAW;IACX,gBAAgB;IAChB,WAAW;IACX,aAAa;IACb,eAAe;IACf,OAAO;IACP,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,cAAc;IACd,cAAc;CACf,CAAC;AAEF,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,KAAK;YACjC,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI;SAC/B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,CAAC,GAAG,cAAc,CAAC,CAAC;IAC/C,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAC,CAAC;AAC1B,CAAC"} |
| export interface OAuthCredentials { | ||
| accessToken: string; | ||
| subscriptionType?: string; | ||
| } | ||
| /** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */ | ||
| export declare function getClaudeDir(): string; | ||
| /** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */ | ||
| export declare function readCredentialsFile(claudeDir?: string): OAuthCredentials | null; | ||
| /** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */ | ||
| export declare function readKeychainToken(): Promise<string | null>; | ||
| /** | ||
| * Get OAuth credentials using platform-appropriate strategy. | ||
| * macOS: Keychain first, then file fallback. Other platforms: file only. | ||
| * Hybrid: if Keychain has token but no subscriptionType, merge from file. | ||
| */ | ||
| export declare function getCredentials(): Promise<OAuthCredentials | null>; | ||
| //# sourceMappingURL=credentials.d.ts.map |
| {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/cli/hud/credentials.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAUD,yEAAyE;AACzE,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAgB/E;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuC1D;AAED;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoBvE"} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { execFile } from 'node:child_process'; | ||
| const KEYCHAIN_TIMEOUT = 3000; // 3s | ||
| const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG; | ||
| function debugLog(msg, data) { | ||
| if (!DEBUG) | ||
| return; | ||
| const entry = { ts: new Date().toISOString(), source: 'credentials', msg, ...data }; | ||
| fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n'); | ||
| } | ||
| /** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */ | ||
| export function getClaudeDir() { | ||
| return (process.env.CLAUDE_CONFIG_DIR || | ||
| path.join(process.env.HOME || homedir(), '.claude')); | ||
| } | ||
| /** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */ | ||
| export function readCredentialsFile(claudeDir) { | ||
| try { | ||
| const dir = claudeDir ?? getClaudeDir(); | ||
| const filePath = path.join(dir, '.credentials.json'); | ||
| const raw = fs.readFileSync(filePath, 'utf-8'); | ||
| const creds = JSON.parse(raw); | ||
| const oauth = creds.claudeAiOauth; | ||
| const accessToken = oauth?.accessToken; | ||
| if (typeof accessToken !== 'string' || !accessToken) | ||
| return null; | ||
| const subscriptionType = typeof oauth?.subscriptionType === 'string' ? oauth.subscriptionType : undefined; | ||
| debugLog('credentials file read', { filePath, hasSubscriptionType: !!subscriptionType }); | ||
| return { accessToken, subscriptionType }; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| /** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */ | ||
| export function readKeychainToken() { | ||
| if (process.platform !== 'darwin') | ||
| return Promise.resolve(null); | ||
| return new Promise((resolve) => { | ||
| execFile('/usr/bin/security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: KEYCHAIN_TIMEOUT }, (err, stdout) => { | ||
| if (err || !stdout.trim()) { | ||
| debugLog('keychain read failed', { error: err?.message }); | ||
| resolve(null); | ||
| return; | ||
| } | ||
| try { | ||
| const parsed = JSON.parse(stdout.trim()); | ||
| const oauth = parsed.claudeAiOauth; | ||
| const token = oauth?.accessToken; | ||
| if (typeof token === 'string' && token) { | ||
| debugLog('keychain token found'); | ||
| resolve(token); | ||
| } | ||
| else { | ||
| debugLog('keychain: no accessToken in parsed data'); | ||
| resolve(null); | ||
| } | ||
| } | ||
| catch { | ||
| // Keychain value might be the raw token string | ||
| const trimmed = stdout.trim(); | ||
| if (trimmed.length > 20) { | ||
| debugLog('keychain: raw token string'); | ||
| resolve(trimmed); | ||
| } | ||
| else { | ||
| debugLog('keychain: unparseable value'); | ||
| resolve(null); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| /** | ||
| * Get OAuth credentials using platform-appropriate strategy. | ||
| * macOS: Keychain first, then file fallback. Other platforms: file only. | ||
| * Hybrid: if Keychain has token but no subscriptionType, merge from file. | ||
| */ | ||
| export async function getCredentials() { | ||
| const fileCreds = readCredentialsFile(); | ||
| if (process.platform !== 'darwin') { | ||
| debugLog('non-darwin: file credentials only', { found: !!fileCreds }); | ||
| return fileCreds; | ||
| } | ||
| // macOS: try Keychain first | ||
| const keychainToken = await readKeychainToken(); | ||
| if (keychainToken) { | ||
| // Merge subscriptionType from file if Keychain doesn't have it | ||
| const subscriptionType = fileCreds?.subscriptionType; | ||
| debugLog('using keychain token', { hasSubscriptionType: !!subscriptionType }); | ||
| return { accessToken: keychainToken, subscriptionType }; | ||
| } | ||
| // Fallback to file | ||
| debugLog('keychain failed, falling back to file', { found: !!fileCreds }); | ||
| return fileCreds; | ||
| } | ||
| //# sourceMappingURL=credentials.js.map |
| {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/cli/hud/credentials.ts"],"names":[],"mappings":"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"} |
| import type { GitStatus } from './types.js'; | ||
| /** | ||
| * Gather git status for the given working directory. | ||
| * Returns null if not in a git repo or on error. | ||
| */ | ||
| export declare function gatherGitStatus(cwd: string): Promise<GitStatus | null>; | ||
| //# sourceMappingURL=git.d.ts.map |
| {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/cli/hud/git.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgB5C;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAsF5E"} |
| import { execFile } from 'node:child_process'; | ||
| const GIT_TIMEOUT = 1000; // 1s per command | ||
| function shellExec(cmd, args, cwd) { | ||
| return new Promise((resolve) => { | ||
| execFile(cmd, args, { cwd, timeout: GIT_TIMEOUT }, (err, stdout) => { | ||
| resolve(err ? '' : stdout.trim()); | ||
| }); | ||
| }); | ||
| } | ||
| function gitExec(args, cwd) { | ||
| return shellExec('git', args, cwd); | ||
| } | ||
| /** | ||
| * Gather git status for the given working directory. | ||
| * Returns null if not in a git repo or on error. | ||
| */ | ||
| export async function gatherGitStatus(cwd) { | ||
| // Check if in a git repo | ||
| const topLevel = await gitExec(['rev-parse', '--show-toplevel'], cwd); | ||
| if (!topLevel) | ||
| return null; | ||
| // Branch name | ||
| const branch = await gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], cwd); | ||
| if (!branch) | ||
| return null; | ||
| // Dirty check | ||
| const statusOutput = await gitExec(['status', '--porcelain', '--no-optional-locks'], cwd); | ||
| let dirty = false; | ||
| let staged = false; | ||
| for (const line of statusOutput.split('\n')) { | ||
| if (line.length < 2) | ||
| continue; | ||
| const index = line[0]; | ||
| const worktree = line[1]; | ||
| // Index column: staged change (A/M/D/R/C) | ||
| if (index !== ' ' && index !== '?') | ||
| staged = true; | ||
| // Worktree column: unstaged change (M/D), or untracked (??) | ||
| if (worktree !== ' ' || index === '?') | ||
| dirty = true; | ||
| } | ||
| // Ahead/behind — detect base branch with layered fallback (ported from statusline.sh) | ||
| const baseBranch = await detectBaseBranch(branch, cwd); | ||
| let ahead = 0; | ||
| let behind = 0; | ||
| if (baseBranch) { | ||
| const revList = await gitExec(['rev-list', '--left-right', '--count', `${baseBranch}...HEAD`], cwd); | ||
| const parts = revList.split(/\s+/); | ||
| if (parts.length === 2) { | ||
| behind = parseInt(parts[0], 10) || 0; | ||
| ahead = parseInt(parts[1], 10) || 0; | ||
| } | ||
| } | ||
| // Diff stats against base | ||
| let filesChanged = 0; | ||
| let additions = 0; | ||
| let deletions = 0; | ||
| if (baseBranch) { | ||
| const diffStat = await gitExec(['diff', '--shortstat', baseBranch], cwd); | ||
| const filesMatch = diffStat.match(/(\d+)\s+file/); | ||
| const addMatch = diffStat.match(/(\d+)\s+insertion/); | ||
| const delMatch = diffStat.match(/(\d+)\s+deletion/); | ||
| filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : 0; | ||
| additions = addMatch ? parseInt(addMatch[1], 10) : 0; | ||
| deletions = delMatch ? parseInt(delMatch[1], 10) : 0; | ||
| } | ||
| // Tag and worktree info (parallel) | ||
| const [tagOutput, worktreeOutput] = await Promise.all([ | ||
| gitExec(['describe', '--tags', '--abbrev=0'], cwd), | ||
| gitExec(['worktree', 'list'], cwd), | ||
| ]); | ||
| const lastTag = tagOutput || null; | ||
| let commitsSinceTag = 0; | ||
| if (lastTag) { | ||
| const countOutput = await gitExec(['rev-list', `${lastTag}..HEAD`, '--count'], cwd); | ||
| commitsSinceTag = parseInt(countOutput, 10) || 0; | ||
| } | ||
| const worktreeCount = worktreeOutput | ||
| ? worktreeOutput.split('\n').filter(l => l.trim().length > 0).length | ||
| : 1; | ||
| return { | ||
| branch, | ||
| dirty, | ||
| staged, | ||
| ahead, | ||
| behind, | ||
| filesChanged, | ||
| additions, | ||
| deletions, | ||
| lastTag, | ||
| commitsSinceTag, | ||
| worktreeCount, | ||
| }; | ||
| } | ||
| /** | ||
| * Detect the base branch for ahead/behind calculations. | ||
| * Uses a 4-layer fallback (ported from statusline.sh): | ||
| * 1. Branch reflog ("Created from") | ||
| * 2. HEAD reflog ("checkout: moving from X to branch") | ||
| * 3. GitHub PR base branch (gh pr view, cached) | ||
| * 4. main/master fallback | ||
| */ | ||
| async function detectBaseBranch(branch, cwd) { | ||
| // Layer 1: branch reflog — look for "branch: Created from" | ||
| const branchLog = await gitExec(['reflog', 'show', branch, '--format=%gs', '-n', '1'], cwd); | ||
| const createdMatch = branchLog.match(/branch: Created from (.+)/); | ||
| if (createdMatch) { | ||
| const candidate = createdMatch[1]; | ||
| if (candidate !== 'HEAD' && !candidate.includes('~')) { | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| } | ||
| // Layer 2: HEAD reflog — look for "checkout: moving from X to branch" | ||
| const headLog = await gitExec(['reflog', 'show', 'HEAD', '--format=%gs'], cwd); | ||
| const escapedBranch = branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| const checkoutPattern = new RegExp(`checkout: moving from (\\S+) to ${escapedBranch}`); | ||
| for (const line of headLog.split('\n')) { | ||
| const match = line.match(checkoutPattern); | ||
| if (match) { | ||
| const candidate = match[1]; | ||
| // Skip raw commit hashes and the current branch | ||
| if (candidate === branch || /^[0-9a-f]{7,}$/.test(candidate)) | ||
| continue; | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| } | ||
| // Layer 3: GitHub PR base branch via gh CLI | ||
| const prBase = await shellExec('gh', ['pr', 'view', '--json', 'baseRefName', '-q', '.baseRefName'], cwd); | ||
| if (prBase) { | ||
| const exists = await gitExec(['rev-parse', '--verify', prBase], cwd); | ||
| if (exists) | ||
| return prBase; | ||
| } | ||
| // Layer 4: main/master fallback (skip if already on that branch — use remote tracking instead) | ||
| for (const candidate of ['main', 'master']) { | ||
| if (candidate === branch) { | ||
| // On main/master itself — compare against remote tracking branch | ||
| const remote = await gitExec(['rev-parse', '--verify', `origin/${candidate}`], cwd); | ||
| if (remote) | ||
| return `origin/${candidate}`; | ||
| continue; | ||
| } | ||
| const exists = await gitExec(['rev-parse', '--verify', candidate], cwd); | ||
| if (exists) | ||
| return candidate; | ||
| } | ||
| return null; | ||
| } | ||
| //# sourceMappingURL=git.js.map |
| {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/cli/hud/git.ts"],"names":[],"mappings":"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"} |
| export {}; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/hud/index.ts"],"names":[],"mappings":""} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import { homedir } from 'node:os'; | ||
| import { readStdin } from './stdin.js'; | ||
| import { loadConfig, resolveComponents } from './config.js'; | ||
| import { gatherGitStatus } from './git.js'; | ||
| import { parseTranscript } from './transcript.js'; | ||
| import { fetchUsageData } from './usage-api.js'; | ||
| import { gatherConfigCounts } from './components/config-counts.js'; | ||
| import { render } from './render.js'; | ||
| const OVERALL_TIMEOUT = 2000; // 2 second overall timeout | ||
| async function main() { | ||
| const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT)); | ||
| try { | ||
| const result = await Promise.race([run(), timeoutPromise]); | ||
| process.stdout.write(result); | ||
| } | ||
| catch { | ||
| // Timeout or error — output nothing (graceful degradation) | ||
| } | ||
| } | ||
| async function run() { | ||
| const stdin = await readStdin(); | ||
| // Debug: dump raw stdin to file when DEVFLOW_HUD_DEBUG is set | ||
| if (process.env.DEVFLOW_HUD_DEBUG) { | ||
| fs.writeFileSync(process.env.DEVFLOW_HUD_DEBUG, JSON.stringify(stdin, null, 2)); | ||
| } | ||
| const config = loadConfig(); | ||
| const resolved = resolveComponents(config); | ||
| const components = new Set(resolved); | ||
| const cwd = stdin.cwd || process.cwd(); | ||
| const devflowDir = process.env.DEVFLOW_DIR || | ||
| path.join(process.env.HOME || homedir(), '.devflow'); | ||
| // Determine what data to gather based on enabled components | ||
| const needsGit = components.has('gitBranch') || | ||
| components.has('gitAheadBehind') || | ||
| components.has('diffStats') || | ||
| components.has('releaseInfo') || | ||
| components.has('worktreeCount'); | ||
| const needsTranscript = components.has('todoProgress') || | ||
| components.has('configCounts'); | ||
| const needsUsage = components.has('usageQuota'); | ||
| const needsConfigCounts = components.has('configCounts'); | ||
| // Parallel data gathering — only fetch what's needed | ||
| const [git, transcript, usage] = await Promise.all([ | ||
| needsGit ? gatherGitStatus(cwd) : Promise.resolve(null), | ||
| needsTranscript && stdin.transcript_path | ||
| ? parseTranscript(stdin.transcript_path) | ||
| : Promise.resolve(null), | ||
| needsUsage ? fetchUsageData() : Promise.resolve(null), | ||
| ]); | ||
| // Session start time from transcript file creation time | ||
| let sessionStartTime = null; | ||
| if (stdin.transcript_path) { | ||
| try { | ||
| const stat = fs.statSync(stdin.transcript_path); | ||
| sessionStartTime = stat.birthtime.getTime(); | ||
| } | ||
| catch { /* file may not exist yet */ } | ||
| } | ||
| // Config counts (fast, synchronous filesystem reads) | ||
| const configCountsData = needsConfigCounts | ||
| ? gatherConfigCounts(cwd) | ||
| : null; | ||
| // Terminal width via stderr (stdout is piped to Claude Code) | ||
| const terminalWidth = process.stderr.columns || 120; | ||
| const ctx = { | ||
| stdin, | ||
| git, | ||
| transcript, | ||
| usage, | ||
| configCounts: configCountsData, | ||
| config: { ...config, components: resolved }, | ||
| devflowDir, | ||
| sessionStartTime, | ||
| terminalWidth, | ||
| }; | ||
| return render(ctx); | ||
| } | ||
| main(); | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/hud/index.ts"],"names":[],"mappings":"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"} |
| import type { GatherContext } from './types.js'; | ||
| /** | ||
| * Render all enabled components into a multi-line HUD string. | ||
| * Components that return null are excluded. Empty lines are skipped. | ||
| */ | ||
| export declare function render(ctx: GatherContext): Promise<string>; | ||
| //# sourceMappingURL=render.d.ts.map |
| {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/cli/hud/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,YAAY,CAAC;AAsDpB;;;GAGG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DhE"} |
| import { dim } from './colors.js'; | ||
| import directory from './components/directory.js'; | ||
| import gitBranch from './components/git-branch.js'; | ||
| import gitAheadBehind from './components/git-ahead-behind.js'; | ||
| import diffStats from './components/diff-stats.js'; | ||
| import model from './components/model.js'; | ||
| import contextUsage from './components/context-usage.js'; | ||
| import versionBadge from './components/version-badge.js'; | ||
| import sessionDuration from './components/session-duration.js'; | ||
| import usageQuota from './components/usage-quota.js'; | ||
| import todoProgress from './components/todo-progress.js'; | ||
| import configCounts from './components/config-counts.js'; | ||
| import sessionCost from './components/session-cost.js'; | ||
| import releaseInfo from './components/release-info.js'; | ||
| import worktreeCount from './components/worktree-count.js'; | ||
| const COMPONENT_MAP = { | ||
| directory, | ||
| gitBranch, | ||
| gitAheadBehind, | ||
| diffStats, | ||
| model, | ||
| contextUsage, | ||
| versionBadge, | ||
| sessionDuration, | ||
| usageQuota, | ||
| todoProgress, | ||
| configCounts, | ||
| sessionCost, | ||
| releaseInfo, | ||
| worktreeCount, | ||
| }; | ||
| /** | ||
| * Line groupings for smart layout. | ||
| * Components are assigned to lines and only rendered if enabled. | ||
| * null entries denote section breaks (blank line between sections). | ||
| */ | ||
| const LINE_GROUPS = [ | ||
| // Section 1: Info (3 lines) | ||
| ['directory', 'gitBranch', 'gitAheadBehind', 'releaseInfo', 'worktreeCount', 'diffStats'], | ||
| ['contextUsage', 'usageQuota'], | ||
| ['model', 'sessionDuration', 'sessionCost', 'configCounts'], | ||
| // --- section break --- | ||
| null, | ||
| // Section 2: Activity | ||
| ['todoProgress'], | ||
| ['versionBadge'], | ||
| ]; | ||
| const SEPARATOR = dim(' \u00B7 '); | ||
| /** | ||
| * Render all enabled components into a multi-line HUD string. | ||
| * Components that return null are excluded. Empty lines are skipped. | ||
| */ | ||
| export async function render(ctx) { | ||
| const enabled = new Set(ctx.config.components); | ||
| // Render all enabled components in parallel | ||
| const results = new Map(); | ||
| const promises = []; | ||
| for (const id of enabled) { | ||
| const fn = COMPONENT_MAP[id]; | ||
| if (!fn) | ||
| continue; | ||
| promises.push(fn(ctx) | ||
| .then((result) => { | ||
| if (result) | ||
| results.set(id, result); | ||
| }) | ||
| .catch(() => { | ||
| /* Component failure is non-fatal */ | ||
| })); | ||
| } | ||
| await Promise.all(promises); | ||
| // Assemble lines using smart layout with section breaks | ||
| const lines = []; | ||
| let pendingBreak = false; | ||
| for (const entry of LINE_GROUPS) { | ||
| if (entry === null) { | ||
| if (lines.length > 0) | ||
| pendingBreak = true; | ||
| continue; | ||
| } | ||
| const lineResults = entry | ||
| .filter((id) => enabled.has(id) && results.has(id)) | ||
| .map((id) => results.get(id)); | ||
| if (lineResults.length > 0) { | ||
| if (pendingBreak) { | ||
| lines.push(''); | ||
| pendingBreak = false; | ||
| } | ||
| // Separate multi-line results (containing newlines) from single-line | ||
| const singleLine = []; | ||
| for (const r of lineResults) { | ||
| if (r.text.includes('\n')) { | ||
| // Flush any accumulated single-line parts first | ||
| if (singleLine.length > 0) { | ||
| lines.push(singleLine.join(SEPARATOR)); | ||
| singleLine.length = 0; | ||
| } | ||
| lines.push(r.text); | ||
| } | ||
| else { | ||
| singleLine.push(r.text); | ||
| } | ||
| } | ||
| if (singleLine.length > 0) { | ||
| lines.push(singleLine.join(SEPARATOR)); | ||
| } | ||
| } | ||
| } | ||
| return lines.join('\n'); | ||
| } | ||
| //# sourceMappingURL=render.js.map |
| {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/cli/hud/render.ts"],"names":[],"mappings":"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"} |
| import type { StdinData } from './types.js'; | ||
| /** | ||
| * Read and parse JSON from stdin. Returns empty object on parse failure. | ||
| */ | ||
| export declare function readStdin(): Promise<StdinData>; | ||
| //# sourceMappingURL=stdin.d.ts.map |
| {"version":3,"file":"stdin.d.ts","sourceRoot":"","sources":["../../src/cli/hud/stdin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAmB9C"} |
| /** | ||
| * Read and parse JSON from stdin. Returns empty object on parse failure. | ||
| */ | ||
| export function readStdin() { | ||
| return new Promise((resolve) => { | ||
| let data = ''; | ||
| process.stdin.setEncoding('utf-8'); | ||
| process.stdin.on('data', (chunk) => { | ||
| data += chunk; | ||
| }); | ||
| process.stdin.on('end', () => { | ||
| try { | ||
| resolve(JSON.parse(data)); | ||
| } | ||
| catch { | ||
| resolve({}); | ||
| } | ||
| }); | ||
| process.stdin.on('error', () => { | ||
| resolve({}); | ||
| }); | ||
| process.stdin.resume(); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=stdin.js.map |
| {"version":3,"file":"stdin.js","sourceRoot":"","sources":["../../src/cli/hud/stdin.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAc,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC7B,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"} |
| import type { TranscriptData } from './types.js'; | ||
| /** | ||
| * Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress. | ||
| * Returns null if the file doesn't exist or can't be parsed. | ||
| */ | ||
| export declare function parseTranscript(transcriptPath: string): Promise<TranscriptData | null>; | ||
| //# sourceMappingURL=transcript.d.ts.map |
| {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/cli/hud/transcript.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAsDhC"} |
| import * as fs from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
| import * as readline from 'node:readline'; | ||
| // Precompiled patterns | ||
| const TODO_WRITE_NAME = /^TodoWrite$/i; | ||
| /** | ||
| * Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress. | ||
| * Returns null if the file doesn't exist or can't be parsed. | ||
| */ | ||
| export async function parseTranscript(transcriptPath) { | ||
| try { | ||
| if (!fs.existsSync(transcriptPath)) | ||
| return null; | ||
| const tools = new Map(); | ||
| const agents = new Map(); | ||
| const skills = new Set(); | ||
| let todosCompleted = 0; | ||
| let todosTotal = 0; | ||
| const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' }); | ||
| const rl = readline.createInterface({ | ||
| input: stream, | ||
| crlfDelay: Infinity, | ||
| }); | ||
| for await (const line of rl) { | ||
| if (!line.trim()) | ||
| continue; | ||
| try { | ||
| const entry = JSON.parse(line); | ||
| // Turn boundary: clear tools and agents on each human message | ||
| // so only the current turn's activity shows in the HUD | ||
| if (entry.type === 'human') { | ||
| tools.clear(); | ||
| agents.clear(); | ||
| continue; | ||
| } | ||
| const todoResult = processEntry(entry, tools, agents, skills); | ||
| if (todoResult) { | ||
| todosCompleted = todoResult.completed; | ||
| todosTotal = todoResult.total; | ||
| } | ||
| } | ||
| catch { | ||
| // Skip malformed lines | ||
| } | ||
| } | ||
| return { | ||
| tools: Array.from(tools.values()), | ||
| agents: Array.from(agents.values()), | ||
| todos: { completed: todosCompleted, total: todosTotal }, | ||
| skills: Array.from(skills), | ||
| }; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| function processEntry(entry, tools, agents, skills) { | ||
| if (entry.type !== 'assistant' || !entry.message) | ||
| return null; | ||
| const message = entry.message; | ||
| if (!Array.isArray(message.content)) | ||
| return null; | ||
| let todoResult = null; | ||
| for (const block of message.content) { | ||
| const blockType = block.type; | ||
| if (blockType === 'tool_use') { | ||
| const name = block.name; | ||
| const id = block.id; | ||
| if (name === 'Agent' || name === 'Task') { | ||
| // Agent spawn | ||
| const input = block.input; | ||
| const agentType = input?.subagent_type || 'Agent'; | ||
| const agentDesc = typeof input?.description === 'string' ? input.description : undefined; | ||
| agents.set(id, { | ||
| name: agentType, | ||
| model: input?.model, | ||
| status: 'running', | ||
| description: agentDesc, | ||
| }); | ||
| } | ||
| else if (name === 'Skill') { | ||
| // Track loaded skills | ||
| const input = block.input; | ||
| if (input?.skill && typeof input.skill === 'string') { | ||
| skills.add(input.skill); | ||
| } | ||
| } | ||
| else if (TODO_WRITE_NAME.test(name)) { | ||
| // Track todos | ||
| const input = block.input; | ||
| const todos = input?.todos; | ||
| if (Array.isArray(todos)) { | ||
| const total = todos.length; | ||
| const completed = todos.filter((t) => t.status === 'completed').length; | ||
| todoResult = { completed, total }; | ||
| } | ||
| } | ||
| else { | ||
| // Regular tool — extract file target for Read/Edit/Write | ||
| const input = block.input; | ||
| let target; | ||
| if (input?.file_path && typeof input.file_path === 'string') { | ||
| target = path.basename(input.file_path); | ||
| } | ||
| // Extract description: prefer input.description, fallback to first 4 words of command (Bash) | ||
| let description; | ||
| if (typeof input?.description === 'string') { | ||
| description = input.description; | ||
| } | ||
| else if (name === 'Bash' && typeof input?.command === 'string') { | ||
| description = input.command.split(/\s+/).slice(0, 4).join(' '); | ||
| } | ||
| tools.set(id, { name, status: 'running', target, description }); | ||
| } | ||
| } | ||
| else if (blockType === 'tool_result') { | ||
| const toolUseId = block.tool_use_id; | ||
| const toolEntry = tools.get(toolUseId); | ||
| if (toolEntry) { | ||
| toolEntry.status = 'completed'; | ||
| } | ||
| const agentEntry = agents.get(toolUseId); | ||
| if (agentEntry) { | ||
| agentEntry.status = 'completed'; | ||
| } | ||
| } | ||
| } | ||
| return todoResult; | ||
| } | ||
| //# sourceMappingURL=transcript.js.map |
| {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/cli/hud/transcript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAG1C,uBAAuB;AACvB,MAAM,eAAe,GAAG,cAAc,CAAC;AAEvC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,cAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;YAAE,OAAO,IAAI,CAAC;QAEhD,MAAM,KAAK,GAAG,IAAI,GAAG,EAGlB,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,GAAG,EAGnB,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1E,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QAEH,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;gBAE1D,8DAA8D;gBAC9D,uDAAuD;gBACvD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC3B,KAAK,CAAC,KAAK,EAAE,CAAC;oBACd,MAAM,CAAC,KAAK,EAAE,CAAC;oBACf,SAAS;gBACX,CAAC;gBAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;gBAC9D,IAAI,UAAU,EAAE,CAAC;oBACf,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC;oBACtC,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC;gBAChC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;QACH,CAAC;QAED,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnC,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE;YACvD,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;SAC3B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAOD,SAAS,YAAY,CACnB,KAA8B,EAC9B,KAA4G,EAC5G,MAGC,EACD,MAAmB;IAEnB,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,OAAO,GAAG,KAAK,CAAC,OAErB,CAAC;IACF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,IAAI,UAAU,GAAsB,IAAI,CAAC;IAEzC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAc,CAAC;QAEvC,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAc,CAAC;YAClC,MAAM,EAAE,GAAG,KAAK,CAAC,EAAY,CAAC;YAE9B,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxC,cAAc;gBACd,MAAM,KAAK,GAAG,KAAK,CAAC,KAA4C,CAAC;gBACjE,MAAM,SAAS,GAAI,KAAK,EAAE,aAAwB,IAAI,OAAO,CAAC;gBAC9D,MAAM,SAAS,GAAG,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;gBACzF,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE;oBACb,IAAI,EAAE,SAAS;oBACf,KAAK,EAAE,KAAK,EAAE,KAA2B;oBACzC,MAAM,EAAE,SAAS;oBACjB,WAAW,EAAE,SAAS;iBACvB,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,sBAAsB;gBACtB,MAAM,KAAK,GAAG,KAAK,CAAC,KAA4C,CAAC;gBACjE,IAAI,KAAK,EAAE,KAAK,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBACpD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;iBAAM,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,cAAc;gBACd,MAAM,KAAK,GAAG,KAAK,CAAC,KAA4C,CAAC;gBACjE,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAC;gBAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;oBAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAC5B,CAAC,CAA0B,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CACzD,CAAC,MAAM,CAAC;oBACT,UAAU,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;gBACpC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,yDAAyD;gBACzD,MAAM,KAAK,GAAG,KAAK,CAAC,KAA4C,CAAC;gBACjE,IAAI,MAA0B,CAAC;gBAC/B,IAAI,KAAK,EAAE,SAAS,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;oBAC5D,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAC1C,CAAC;gBACD,6FAA6F;gBAC7F,IAAI,WAA+B,CAAC;gBACpC,IAAI,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,EAAE,CAAC;oBAC3C,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;gBAClC,CAAC;qBAAM,IAAI,IAAI,KAAK,MAAM,IAAI,OAAO,KAAK,EAAE,OAAO,KAAK,QAAQ,EAAE,CAAC;oBACjE,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC;gBACD,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;aAAM,IAAI,SAAS,KAAK,aAAa,EAAE,CAAC;YACvC,MAAM,SAAS,GAAG,KAAK,CAAC,WAAqB,CAAC;YAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvC,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC;YACjC,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACzC,IAAI,UAAU,EAAE,CAAC;gBACf,UAAU,CAAC,MAAM,GAAG,WAAW,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"} |
| /** | ||
| * StdinData — the JSON that Claude Code pipes to statusLine commands. | ||
| */ | ||
| export interface StdinData { | ||
| model?: { | ||
| display_name?: string; | ||
| id?: string; | ||
| }; | ||
| cwd?: string; | ||
| context_window?: { | ||
| context_window_size?: number; | ||
| current_usage?: { | ||
| input_tokens?: number; | ||
| output_tokens?: number; | ||
| }; | ||
| used_percentage?: number; | ||
| }; | ||
| cost?: { | ||
| total_cost_usd?: number; | ||
| }; | ||
| session_id?: string; | ||
| transcript_path?: string; | ||
| } | ||
| /** | ||
| * Component IDs — the 14 HUD components. | ||
| */ | ||
| export type ComponentId = 'directory' | 'gitBranch' | 'gitAheadBehind' | 'diffStats' | 'model' | 'contextUsage' | 'versionBadge' | 'sessionDuration' | 'usageQuota' | 'todoProgress' | 'configCounts' | 'sessionCost' | 'releaseInfo' | 'worktreeCount'; | ||
| /** | ||
| * HUD config persisted to ~/.devflow/hud.json. | ||
| */ | ||
| export interface HudConfig { | ||
| enabled: boolean; | ||
| detail: boolean; | ||
| } | ||
| /** | ||
| * Component render result. | ||
| */ | ||
| export interface ComponentResult { | ||
| text: string; | ||
| raw: string; | ||
| } | ||
| /** | ||
| * Component function signature. | ||
| */ | ||
| export type ComponentFn = (ctx: GatherContext) => Promise<ComponentResult | null>; | ||
| /** | ||
| * Git status data gathered from the working directory. | ||
| */ | ||
| export interface GitStatus { | ||
| branch: string; | ||
| dirty: boolean; | ||
| staged: boolean; | ||
| ahead: number; | ||
| behind: number; | ||
| filesChanged: number; | ||
| additions: number; | ||
| deletions: number; | ||
| lastTag: string | null; | ||
| commitsSinceTag: number; | ||
| worktreeCount: number; | ||
| } | ||
| /** | ||
| * Transcript data parsed from session JSONL. | ||
| */ | ||
| export interface TranscriptData { | ||
| tools: Array<{ | ||
| name: string; | ||
| status: 'running' | 'completed'; | ||
| target?: string; | ||
| description?: string; | ||
| }>; | ||
| agents: Array<{ | ||
| name: string; | ||
| model?: string; | ||
| status: 'running' | 'completed'; | ||
| description?: string; | ||
| }>; | ||
| todos: { | ||
| completed: number; | ||
| total: number; | ||
| }; | ||
| skills: string[]; | ||
| } | ||
| /** | ||
| * Usage API data. | ||
| */ | ||
| export interface UsageData { | ||
| fiveHourPercent: number | null; | ||
| sevenDayPercent: number | null; | ||
| } | ||
| /** | ||
| * Config counts data for the configCounts component. | ||
| */ | ||
| export interface ConfigCountsData { | ||
| claudeMdFiles: number; | ||
| rules: number; | ||
| mcpServers: number; | ||
| hooks: number; | ||
| } | ||
| /** | ||
| * Gather context passed to all component render functions. | ||
| */ | ||
| export interface GatherContext { | ||
| stdin: StdinData; | ||
| git: GitStatus | null; | ||
| transcript: TranscriptData | null; | ||
| usage: UsageData | null; | ||
| configCounts: ConfigCountsData | null; | ||
| config: HudConfig & { | ||
| components: ComponentId[]; | ||
| }; | ||
| devflowDir: string; | ||
| sessionStartTime: number | null; | ||
| terminalWidth: number; | ||
| } | ||
| //# sourceMappingURL=types.d.ts.map |
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/hud/types.ts"],"names":[],"mappings":"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"} |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/hud/types.ts"],"names":[],"mappings":""} |
| import type { UsageData } from './types.js'; | ||
| /** | ||
| * Fetch usage quota data from the Anthropic API. | ||
| * Uses caching with backoff for rate limiting. Returns null on failure. | ||
| */ | ||
| export declare function fetchUsageData(): Promise<UsageData | null>; | ||
| //# sourceMappingURL=usage-api.d.ts.map |
| {"version":3,"file":"usage-api.d.ts","sourceRoot":"","sources":["../../src/cli/hud/usage-api.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAqB5C;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAiFhE"} |
| import * as fs from 'node:fs'; | ||
| import { readCache, writeCache, readCacheStale } from './cache.js'; | ||
| import { getCredentials } from './credentials.js'; | ||
| const USAGE_CACHE_KEY = 'usage'; | ||
| const USAGE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes | ||
| const USAGE_FAIL_TTL = 15 * 1000; // 15 seconds | ||
| const API_TIMEOUT = 1_500; // Must fit within 2s overall HUD timeout | ||
| const BACKOFF_CACHE_KEY = 'usage-backoff'; | ||
| const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG; | ||
| function debugLog(msg, data) { | ||
| if (!DEBUG) | ||
| return; | ||
| const entry = { ts: new Date().toISOString(), source: 'usage-api', msg, ...data }; | ||
| fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n'); | ||
| } | ||
| /** | ||
| * Fetch usage quota data from the Anthropic API. | ||
| * Uses caching with backoff for rate limiting. Returns null on failure. | ||
| */ | ||
| export async function fetchUsageData() { | ||
| // Check backoff | ||
| const backoff = readCache(BACKOFF_CACHE_KEY); | ||
| if (backoff && Date.now() < backoff.retryAfter) { | ||
| debugLog('skipped: backoff active', { retryAfter: backoff.retryAfter }); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| // Check cache | ||
| const cached = readCache(USAGE_CACHE_KEY); | ||
| if (cached) | ||
| return cached; | ||
| const creds = await getCredentials(); | ||
| if (!creds) { | ||
| debugLog('no OAuth credentials found'); | ||
| return null; | ||
| } | ||
| const token = creds.accessToken; | ||
| try { | ||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), API_TIMEOUT); | ||
| debugLog('fetching usage', { timeout: API_TIMEOUT }); | ||
| const response = await fetch('https://api.anthropic.com/api/oauth/usage', { | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| 'Content-Type': 'application/json', | ||
| 'anthropic-beta': 'oauth-2025-04-20', | ||
| }, | ||
| signal: controller.signal, | ||
| }); | ||
| clearTimeout(timeout); | ||
| if (response.status === 429) { | ||
| const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10); | ||
| const delay = Math.min(retryAfter * 1000, 5 * 60 * 1000); | ||
| writeCache(BACKOFF_CACHE_KEY, { retryAfter: Date.now() + delay, delay }, delay); | ||
| debugLog('rate limited (429)', { retryAfter, delay }); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| if (!response.ok) { | ||
| debugLog('non-200 response', { status: response.status, statusText: response.statusText }); | ||
| writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| const body = (await response.json()); | ||
| const fiveHour = body.five_hour; | ||
| const sevenDay = body.seven_day; | ||
| const data = { | ||
| fiveHourPercent: typeof fiveHour?.utilization === 'number' | ||
| ? Math.round(Math.max(0, Math.min(100, fiveHour.utilization))) | ||
| : null, | ||
| sevenDayPercent: typeof sevenDay?.utilization === 'number' | ||
| ? Math.round(Math.max(0, Math.min(100, sevenDay.utilization))) | ||
| : null, | ||
| }; | ||
| debugLog('usage fetched', { fiveHour: data.fiveHourPercent, sevenDay: data.sevenDayPercent }); | ||
| writeCache(USAGE_CACHE_KEY, data, USAGE_CACHE_TTL); | ||
| return data; | ||
| } | ||
| catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| debugLog('fetch failed', { error: message }); | ||
| writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL); | ||
| return readCacheStale(USAGE_CACHE_KEY); | ||
| } | ||
| } | ||
| //# sourceMappingURL=usage-api.js.map |
| {"version":3,"file":"usage-api.js","sourceRoot":"","sources":["../../src/cli/hud/usage-api.ts"],"names":[],"mappings":"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"} |
| # Stub Detection Patterns | ||
| Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues. | ||
| Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns. | ||
| --- | ||
| ## 1. Component Stubs | ||
| Render nothing meaningful or return placeholder markup. | ||
| ```tsx | ||
| // STUB — returns static text, no real rendering | ||
| function UserProfile({ userId }: Props) { | ||
| return <div>Name</div>; | ||
| } | ||
| // REAL — fetches and renders actual data | ||
| function UserProfile({ userId }: Props) { | ||
| const user = useUser(userId); | ||
| if (!user) return <Skeleton />; | ||
| return <div><h2>{user.name}</h2><p>{user.email}</p></div>; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `return null` / `return <></>` in components that should render content | ||
| - Empty function bodies (`{}`) for handlers or lifecycle methods | ||
| - Components returning only hardcoded strings with no data binding | ||
| --- | ||
| ## 2. API / Service Stubs | ||
| Functions that exist in signature but throw, return hardcoded values, or do nothing. | ||
| ```typescript | ||
| // STUB — throws instead of implementing | ||
| async function createOrder(items: CartItem[]): Promise<Order> { | ||
| throw new Error("TODO: implement"); | ||
| } | ||
| // STUB — hardcoded return, no real logic | ||
| async function getUser(id: string): Promise<User> { | ||
| return { id, name: "Test User", email: "test@test.com" }; | ||
| } | ||
| // REAL — actual implementation | ||
| async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> { | ||
| const validated = validateItems(items); | ||
| if (!validated.ok) return validated; | ||
| const order = await db.orders.create({ items: validated.value }); | ||
| await queue.publish("order.created", order); | ||
| return { ok: true, value: order }; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `throw new Error("TODO")` / `throw new Error("Not implemented")` | ||
| - Functions returning hardcoded objects (no DB/API/computation) | ||
| - `"Not implemented"` strings in response bodies | ||
| - Empty async functions (`async function foo() {}`) | ||
| --- | ||
| ## 3. Hook / Effect Stubs | ||
| State and effects declared but not wired to behavior. | ||
| ```typescript | ||
| // STUB — effect does nothing | ||
| useEffect(() => {}, [userId]); | ||
| // STUB — state declared, setter never called | ||
| const [items, setItems] = useState<Item[]>([]); | ||
| // ... setItems never appears in the component | ||
| // STUB — custom hook returns static value | ||
| function usePermissions(): Permissions { | ||
| return { canEdit: true, canDelete: false }; | ||
| } | ||
| // REAL — custom hook with actual logic | ||
| function usePermissions(): Permissions { | ||
| const { user } = useAuth(); | ||
| const { data } = useQuery(["permissions", user.role], fetchPermissions); | ||
| return data ?? DEFAULT_PERMISSIONS; | ||
| } | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `useEffect(() => {}, [...])` — empty effect body | ||
| - `useState` where the setter is never called in the component | ||
| - Custom hooks returning static/hardcoded values | ||
| - `useMemo`/`useCallback` wrapping static values | ||
| --- | ||
| ## 4. Wiring Gaps | ||
| Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end. | ||
| ```typescript | ||
| // GAP — fetch without await (result discarded) | ||
| function loadDashboard() { | ||
| fetchMetrics(); // Promise floats, never awaited | ||
| return <Dashboard />; | ||
| } | ||
| // GAP — state declared but never rendered | ||
| const [error, setError] = useState<string | null>(null); | ||
| // ... error never appears in JSX | ||
| // GAP — handler defined but not bound | ||
| function handleSubmit(data: FormData) { /* real logic */ } | ||
| // ... <form onSubmit={handleSubmit}> never appears | ||
| // GAP — route defined with no-op handler | ||
| app.post("/api/orders", (_req, res) => { | ||
| res.status(200).json({ ok: true }); | ||
| }); | ||
| // GAP — env var read but unused | ||
| const API_KEY = process.env.STRIPE_API_KEY; | ||
| // ... API_KEY never passed to any client or fetch call | ||
| ``` | ||
| **Patterns to flag:** | ||
| - `fetch`/`axios`/API call without `await` or `.then` (result discarded) | ||
| - State variable (`useState`, `useRef`) never rendered or read in output | ||
| - Event handler defined but not bound to any element/listener | ||
| - Route/endpoint registered with empty or no-op handler | ||
| - Environment variable or config read but never used downstream | ||
| - Import used only in type position but imported as value (in non-type-only import) |
+31
-0
@@ -8,2 +8,32 @@ # Changelog | ||
| ## [1.8.0] - 2026-03-22 | ||
| ### Added | ||
| - **Configurable HUD** replacing bash statusline — 14 components, on/off model (#155) | ||
| - **HUD components**: directory, git branch, ahead/behind, diff stats, release info, worktree count, model, context usage, version badge, session duration, session cost, usage quota, todo progress, config counts | ||
| - **`--hud-only` flag** for standalone HUD install | ||
| - **`--no-hud` flag** to skip HUD during init | ||
| - **`devflow hud` command** (--status, --enable, --disable, --detail, --no-detail) | ||
| - **Version upgrade notification**: `✦ Devflow vX.Y.Z · update: npx devflow-kit init` (yellow, always visible even when HUD disabled) | ||
| - **Skill shadowing docs** and HUD options added to README (#156) | ||
| - **Simplifier agent** — 8 structured slop detection categories (#120) | ||
| - **Scrutinizer agent** — stub detection patterns with reference file (#121) | ||
| - **Shepherd agent** — goal-backward verification, artifact depth checking, stub type, re-verification (#124) | ||
| ### Changed | ||
| - Init flow: HUD preset picker (5 options) → simple yes/no confirm | ||
| - `--disable` keeps statusLine registered (version badge still renders) | ||
| - Manifest `features.hud` field: `string|false` → `boolean` | ||
| ### Fixed | ||
| - HUD base branch detection matching raw commit hashes from reflog (#156) | ||
| - HUD comparing main vs main (0/0 always) — now compares against origin/main | ||
| ### Removed | ||
| - HUD preset system (minimal/classic/standard/full) | ||
| - `--configure`, `--preset`, `--hud <preset>` flags | ||
| - `speed`, `tool-activity`, `agent-activity` components | ||
| --- | ||
| ## [1.7.0] - 2026-03-20 | ||
@@ -932,2 +962,3 @@ | ||
| [Unreleased]: https://github.com/dean0x/devflow/compare/v1.4.0...HEAD | ||
| [1.8.0]: https://github.com/dean0x/devflow/compare/v1.7.0...v1.8.0 | ||
| [1.7.0]: https://github.com/dean0x/devflow/compare/v1.6.1...v1.7.0 | ||
@@ -934,0 +965,0 @@ [1.6.1]: https://github.com/dean0x/devflow/compare/v1.6.0...v1.6.1 |
+3
-1
@@ -12,2 +12,3 @@ #!/usr/bin/env node | ||
| import { skillsCommand } from './commands/skills.js'; | ||
| import { hudCommand } from './commands/hud.js'; | ||
| const __filename = fileURLToPath(import.meta.url); | ||
@@ -23,3 +24,3 @@ const __dirname = dirname(__filename); | ||
| .helpOption('-h, --help', 'Display help information') | ||
| .addHelpText('after', '\nExamples:\n $ devflow init Install all DevFlow plugins\n $ devflow init --plugin=implement Install specific plugin\n $ devflow init --plugin=implement,review Install multiple plugins\n $ devflow list List available plugins\n $ devflow ambient --enable Enable always-on ambient mode\n $ devflow memory --status Check working memory state\n $ devflow uninstall Remove DevFlow from Claude Code\n $ devflow --version Show version\n $ devflow --help Show help\n\nDocumentation:\n https://github.com/dean0x/devflow#readme'); | ||
| .addHelpText('after', '\nExamples:\n $ devflow init Install all DevFlow plugins\n $ devflow init --plugin=implement Install specific plugin\n $ devflow init --plugin=implement,review Install multiple plugins\n $ devflow list List available plugins\n $ devflow ambient --enable Enable always-on ambient mode\n $ devflow memory --status Check working memory state\n $ devflow hud --configure Configure HUD preset\n $ devflow uninstall Remove DevFlow from Claude Code\n $ devflow --version Show version\n $ devflow --help Show help\n\nDocumentation:\n https://github.com/dean0x/devflow#readme'); | ||
| // Register commands | ||
@@ -32,2 +33,3 @@ program.addCommand(initCommand); | ||
| program.addCommand(skillsCommand); | ||
| program.addCommand(hudCommand); | ||
| // Handle no command | ||
@@ -34,0 +36,0 @@ program.action(() => { |
@@ -6,2 +6,3 @@ import { Command } from 'commander'; | ||
| export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js'; | ||
| export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js'; | ||
| /** | ||
@@ -8,0 +9,0 @@ * Parse a comma-separated plugin selection string into normalized plugin names. |
+112
-3
@@ -11,3 +11,3 @@ import { Command } from 'commander'; | ||
| import { isClaudeCliAvailable } from '../utils/cli.js'; | ||
| import { installViaCli, installViaFileCopy } from '../utils/installer.js'; | ||
| import { installViaCli, installViaFileCopy, copyDirectory } from '../utils/installer.js'; | ||
| import { installSettings, installManagedSettings, installClaudeignore, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js'; | ||
@@ -19,2 +19,4 @@ import { DEVFLOW_PLUGINS, LEGACY_SKILL_NAMES, LEGACY_COMMAND_NAMES, buildAssetMaps } from '../plugins.js'; | ||
| import { addMemoryHooks, removeMemoryHooks } from './memory.js'; | ||
| import { addHudStatusLine, removeHudStatusLine } from './hud.js'; | ||
| import { loadConfig as loadHudConfig, saveConfig as saveHudConfig } from '../hud/config.js'; | ||
| import { readManifest, writeManifest, resolvePluginList, detectUpgrade } from '../utils/manifest.js'; | ||
@@ -25,2 +27,3 @@ // Re-export pure functions for tests (canonical source is post-install.ts) | ||
| export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js'; | ||
| export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js'; | ||
| const __filename = fileURLToPath(import.meta.url); | ||
@@ -72,2 +75,4 @@ const __dirname = dirname(__filename); | ||
| .option('--no-memory', 'Disable working memory hooks') | ||
| .option('--no-hud', 'Disable HUD status line') | ||
| .option('--hud-only', 'Install only the HUD (no plugins, hooks, or extras)') | ||
| .action(async (options) => { | ||
@@ -89,3 +94,7 @@ // Get package version | ||
| let scope = 'user'; | ||
| if (options.scope) { | ||
| if (options.hudOnly) { | ||
| // --hud-only: skip scope prompt, always user scope | ||
| scope = 'user'; | ||
| } | ||
| else if (options.scope) { | ||
| const normalizedScope = options.scope.toLowerCase(); | ||
@@ -116,2 +125,66 @@ if (normalizedScope !== 'user' && normalizedScope !== 'local') { | ||
| } | ||
| // --hud-only: install only HUD (skip plugins, hooks, extras) | ||
| if (options.hudOnly) { | ||
| // Resolve paths | ||
| const paths = await getInstallationPaths(scope); | ||
| const claudeDir = paths.claudeDir; | ||
| const devflowDir = paths.devflowDir; | ||
| // Save HUD config | ||
| const existingHud = loadHudConfig(); | ||
| saveHudConfig({ enabled: true, detail: existingHud.detail }); | ||
| // Update statusLine in settings.json | ||
| const settingsPath = path.join(claudeDir, 'settings.json'); | ||
| try { | ||
| let content; | ||
| try { | ||
| content = await fs.readFile(settingsPath, 'utf-8'); | ||
| } | ||
| catch { | ||
| content = '{}'; | ||
| } | ||
| const updated = addHudStatusLine(content, devflowDir); | ||
| await fs.writeFile(settingsPath, updated, 'utf-8'); | ||
| } | ||
| catch (error) { | ||
| p.log.error(`Failed to update settings: ${error instanceof Error ? error.message : error}`); | ||
| process.exit(1); | ||
| } | ||
| // Copy HUD scripts to devflow dir | ||
| const rootDir = path.resolve(__dirname, '..', '..'); | ||
| const scriptsSource = path.join(rootDir, 'scripts'); | ||
| const scriptsTarget = path.join(devflowDir, 'scripts'); | ||
| try { | ||
| await fs.mkdir(scriptsTarget, { recursive: true }); | ||
| // Copy hud.sh | ||
| await fs.copyFile(path.join(scriptsSource, 'hud.sh'), path.join(scriptsTarget, 'hud.sh')); | ||
| // Copy hud/ directory | ||
| const hudSource = path.join(scriptsSource, 'hud'); | ||
| const hudTarget = path.join(scriptsTarget, 'hud'); | ||
| await copyDirectory(hudSource, hudTarget); | ||
| if (process.platform !== 'win32') { | ||
| await fs.chmod(path.join(scriptsTarget, 'hud.sh'), 0o755); | ||
| } | ||
| } | ||
| catch (error) { | ||
| p.log.error(`Failed to copy HUD scripts: ${error instanceof Error ? error.message : error}`); | ||
| process.exit(1); | ||
| } | ||
| // Write minimal manifest | ||
| const now = new Date().toISOString(); | ||
| try { | ||
| await writeManifest(devflowDir, { | ||
| version, | ||
| plugins: [], | ||
| scope, | ||
| features: { teams: false, ambient: false, memory: false, hud: true }, | ||
| installedAt: now, | ||
| updatedAt: now, | ||
| }); | ||
| } | ||
| catch { /* non-fatal */ } | ||
| p.log.success('HUD installed'); | ||
| p.log.info(`Configure later: ${color.cyan('devflow hud --status')}`); | ||
| p.outro(color.green('HUD-only install complete.')); | ||
| return; | ||
| } | ||
| // Select plugins to install | ||
@@ -214,2 +287,21 @@ let selectedPlugins = []; | ||
| } | ||
| // HUD selection (yes/no) | ||
| let hudEnabled; | ||
| if (options.hud !== undefined) { | ||
| hudEnabled = options.hud; | ||
| } | ||
| else if (!process.stdin.isTTY) { | ||
| hudEnabled = true; | ||
| } | ||
| else { | ||
| const hudChoice = await p.confirm({ | ||
| message: 'Enable HUD status line?', | ||
| initialValue: true, | ||
| }); | ||
| if (p.isCancel(hudChoice)) { | ||
| p.cancel('Installation cancelled.'); | ||
| process.exit(0); | ||
| } | ||
| hudEnabled = hudChoice; | ||
| } | ||
| // Security deny list placement (user scope + TTY only) | ||
@@ -452,2 +544,19 @@ let securityMode = 'user'; | ||
| } | ||
| // Configure HUD | ||
| const existingHud = loadHudConfig(); | ||
| saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail }); | ||
| // Update statusLine in settings.json (add or remove based on choice) | ||
| try { | ||
| const hudContent = await fs.readFile(settingsPath, 'utf-8'); | ||
| const hudUpdated = hudEnabled | ||
| ? addHudStatusLine(hudContent, devflowDir) | ||
| : removeHudStatusLine(hudContent); | ||
| if (hudUpdated !== hudContent) { | ||
| await fs.writeFile(settingsPath, hudUpdated, 'utf-8'); | ||
| if (verbose) { | ||
| p.log.info(`HUD ${hudEnabled ? 'enabled' : 'disabled'}`); | ||
| } | ||
| } | ||
| } | ||
| catch { /* settings.json may not exist yet */ } | ||
| } | ||
@@ -553,3 +662,3 @@ const fileExtras = selectedExtras.filter(e => e !== 'settings' && e !== 'safe-delete'); | ||
| scope, | ||
| features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled }, | ||
| features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled, hud: hudEnabled }, | ||
| installedAt: existingManifest?.installedAt ?? now, | ||
@@ -556,0 +665,0 @@ updatedAt: now, |
@@ -18,2 +18,3 @@ import { Command } from 'commander'; | ||
| features.memory ? 'memory' : null, | ||
| features.hud ? 'hud' : null, | ||
| ].filter(Boolean).join(', ') || 'none'; | ||
@@ -20,0 +21,0 @@ } |
@@ -14,2 +14,3 @@ import { Command } from 'commander'; | ||
| import { removeMemoryHooks } from './memory.js'; | ||
| import { removeHudStatusLine } from './hud.js'; | ||
| import { listShadowed } from './skills.js'; | ||
@@ -364,21 +365,13 @@ import { detectShell, getProfilePath } from '../utils/safe-delete.js'; | ||
| const settingsPath = path.join(paths.claudeDir, 'settings.json'); | ||
| let settingsContent = await fs.readFile(settingsPath, 'utf-8'); | ||
| // Always remove ambient hook on full uninstall (idempotent) | ||
| const withoutAmbient = removeAmbientHook(settingsContent); | ||
| if (withoutAmbient !== settingsContent) { | ||
| await fs.writeFile(settingsPath, withoutAmbient, 'utf-8'); | ||
| settingsContent = withoutAmbient; | ||
| const originalContent = await fs.readFile(settingsPath, 'utf-8'); | ||
| // Remove all DevFlow hooks in one pass (idempotent) | ||
| let settingsContent = removeAmbientHook(originalContent); | ||
| settingsContent = removeMemoryHooks(settingsContent); | ||
| settingsContent = removeHudStatusLine(settingsContent); | ||
| if (settingsContent !== originalContent) { | ||
| await fs.writeFile(settingsPath, settingsContent, 'utf-8'); | ||
| if (verbose) { | ||
| p.log.success(`Ambient mode hook removed from settings.json (${scope})`); | ||
| p.log.success(`DevFlow hooks removed from settings.json (${scope})`); | ||
| } | ||
| } | ||
| // Always remove memory hooks on full uninstall (idempotent) | ||
| const withoutMemory = removeMemoryHooks(settingsContent); | ||
| if (withoutMemory !== settingsContent) { | ||
| await fs.writeFile(settingsPath, withoutMemory, 'utf-8'); | ||
| settingsContent = withoutMemory; | ||
| if (verbose) { | ||
| p.log.success(`Memory hooks removed from settings.json (${scope})`); | ||
| } | ||
| } | ||
| const settings = JSON.parse(settingsContent); | ||
@@ -385,0 +378,0 @@ if (settings.hooks) { |
@@ -12,2 +12,3 @@ /** | ||
| memory: boolean; | ||
| hud?: boolean; | ||
| }; | ||
@@ -14,0 +15,0 @@ installedAt: string; |
+5
-2
| { | ||
| "name": "devflow-kit", | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "description": "Agentic Development Toolkit for Claude Code - Enhance AI-assisted development with intelligent commands and workflows", | ||
@@ -15,2 +15,4 @@ "type": "module", | ||
| "scripts/hooks/", | ||
| "scripts/hud.sh", | ||
| "scripts/hud/", | ||
| "src/templates/", | ||
@@ -22,5 +24,6 @@ "README.md", | ||
| "scripts": { | ||
| "build": "npm run build:cli && npm run build:plugins", | ||
| "build": "npm run build:cli && npm run build:plugins && npm run build:hud", | ||
| "build:cli": "tsc", | ||
| "build:plugins": "npx tsx scripts/build-plugins.ts", | ||
| "build:hud": "node scripts/build-hud.js", | ||
| "dev": "tsc --watch", | ||
@@ -27,0 +30,0 @@ "cli": "node dist/cli.js", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -24,10 +24,12 @@ --- | ||
| 3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues. | ||
| 4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 6. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| 6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 7. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| ## Principles | ||
@@ -34,0 +36,0 @@ |
@@ -24,6 +24,16 @@ --- | ||
| 2. **Review implementation**: Read FILES_CHANGED to understand what was built | ||
| 3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 4. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 5. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| 3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth. | ||
| 4. **Check artifact depth**: Classify each deliverable using this scale: | ||
| | Depth | Meaning | Example | | ||
| |-------|---------|---------| | ||
| | Exists | File/function created | Route file exists | | ||
| | Substantive | Contains real logic | Route has validation + DB call | | ||
| | Wired | Connected to running app | Route registered, imported, reachable | | ||
| Flag anything at "Exists" without reaching "Wired" as `incomplete`. | ||
| 5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 6. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 7. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| ## Principles | ||
@@ -55,2 +65,7 @@ | ||
| ### Artifact Depth | ||
| | Deliverable | Exists | Substantive | Wired | Status | | ||
| |-------------|--------|-------------|-------|--------| | ||
| | {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub | | ||
| ### Misalignments Found (if MISALIGNED) | ||
@@ -64,2 +79,3 @@ | ||
| | intent_drift | {how intent drifted} | {file paths} | {how to realign} | | ||
| | stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} | | ||
@@ -69,13 +85,9 @@ ### Scope Check | ||
| - Justification: {if additions found, are they justified design improvements?} | ||
| ### Re-verification (if applicable) | ||
| | Previously Failed | Status | Notes | | ||
| |-------------------|--------|-------| | ||
| | {item} | RESOLVED/STILL_FAILING | {details} | | ||
| ``` | ||
| ## Misalignment Types | ||
| | Type | Description | Example | | ||
| |------|-------------|---------| | ||
| | `missing` | Functionality in plan not implemented | "Login validation not implemented" | | ||
| | `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" | | ||
| | `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" | | ||
| | `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" | | ||
| ## Boundaries | ||
@@ -88,2 +100,3 @@ | ||
| - Intent drift | ||
| - Stubs or placeholders passing as real implementations | ||
@@ -95,2 +108,3 @@ **Report as ALIGNED:** | ||
| - Implementation matches original intent | ||
| - All deliverables reach "Wired" depth | ||
@@ -97,0 +111,0 @@ **Never:** |
@@ -33,22 +33,30 @@ --- | ||
| 3. **Enhance Clarity**: Simplify code structure by: | ||
| 3. **Remove Slop**: Detect and remove these categories: | ||
| - Reducing unnecessary complexity and nesting | ||
| - Eliminating redundant code and abstractions | ||
| - Improving readability through clear variable and function names | ||
| | Category | Pattern | | ||
| |----------|---------| | ||
| | Language-behavior tests | Tests verifying built-in language features work as documented | | ||
| | Redundant type checks | Runtime checks for types TypeScript already enforces | | ||
| | Over-defensive handling | try/catch around code that cannot throw | | ||
| | Debug remnants | console.log, debugger, alert() left behind | | ||
| | Commented-out code | Dead code preserved in comments | | ||
| | Unused imports | Imports not referenced anywhere in file | | ||
| | Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) | | ||
| | Unnecessary intermediates | Variables used once, immediately after assignment | | ||
| 4. **Enhance Clarity**: Simplify code structure by: | ||
| - Reducing unnecessary nesting (early returns, guard clauses) | ||
| - Consolidating related logic | ||
| - Removing unnecessary comments that describe obvious code | ||
| - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions | ||
| - Choose clarity over brevity - explicit code is often better than overly compact code | ||
| - Avoiding nested ternary operators — prefer switch or if/else | ||
| - Choosing clarity over brevity — explicit code beats compact code | ||
| 4. **Maintain Balance**: Avoid over-simplification that could: | ||
| 5. **Maintain Balance**: Avoid over-simplification that could: | ||
| - Reduce code clarity or maintainability | ||
| - Create overly clever solutions that are hard to understand | ||
| - Combine too many concerns into single functions or components | ||
| - Remove helpful abstractions that improve code organization | ||
| - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) | ||
| - Make the code harder to debug or extend | ||
| 5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
| 6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
@@ -58,3 +66,3 @@ Your refinement process: | ||
| 1. Identify the recently modified code sections | ||
| 2. Analyze for opportunities to improve elegance and consistency | ||
| 2. Analyze for slop categories and clarity improvements | ||
| 3. Apply project-specific best practices and coding standards | ||
@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged | ||
| **Handle autonomously:** | ||
| - Naming improvements, dead code removal, nesting reduction | ||
| - Slop removal (all 8 categories) | ||
| - Naming improvements, nesting reduction | ||
| - Import sorting and organization | ||
| - Redundant abstraction elimination | ||
| - Comment cleanup (remove obvious, keep non-obvious) | ||
| - Comment cleanup (remove obvious, keep non-obvious) |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -24,10 +24,12 @@ --- | ||
| 3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues. | ||
| 4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 6. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| 6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 7. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| ## Principles | ||
@@ -34,0 +36,0 @@ |
@@ -24,6 +24,16 @@ --- | ||
| 2. **Review implementation**: Read FILES_CHANGED to understand what was built | ||
| 3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 4. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 5. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| 3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth. | ||
| 4. **Check artifact depth**: Classify each deliverable using this scale: | ||
| | Depth | Meaning | Example | | ||
| |-------|---------|---------| | ||
| | Exists | File/function created | Route file exists | | ||
| | Substantive | Contains real logic | Route has validation + DB call | | ||
| | Wired | Connected to running app | Route registered, imported, reachable | | ||
| Flag anything at "Exists" without reaching "Wired" as `incomplete`. | ||
| 5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 6. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 7. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| ## Principles | ||
@@ -55,2 +65,7 @@ | ||
| ### Artifact Depth | ||
| | Deliverable | Exists | Substantive | Wired | Status | | ||
| |-------------|--------|-------------|-------|--------| | ||
| | {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub | | ||
| ### Misalignments Found (if MISALIGNED) | ||
@@ -64,2 +79,3 @@ | ||
| | intent_drift | {how intent drifted} | {file paths} | {how to realign} | | ||
| | stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} | | ||
@@ -69,13 +85,9 @@ ### Scope Check | ||
| - Justification: {if additions found, are they justified design improvements?} | ||
| ### Re-verification (if applicable) | ||
| | Previously Failed | Status | Notes | | ||
| |-------------------|--------|-------| | ||
| | {item} | RESOLVED/STILL_FAILING | {details} | | ||
| ``` | ||
| ## Misalignment Types | ||
| | Type | Description | Example | | ||
| |------|-------------|---------| | ||
| | `missing` | Functionality in plan not implemented | "Login validation not implemented" | | ||
| | `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" | | ||
| | `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" | | ||
| | `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" | | ||
| ## Boundaries | ||
@@ -88,2 +100,3 @@ | ||
| - Intent drift | ||
| - Stubs or placeholders passing as real implementations | ||
@@ -95,2 +108,3 @@ **Report as ALIGNED:** | ||
| - Implementation matches original intent | ||
| - All deliverables reach "Wired" depth | ||
@@ -97,0 +111,0 @@ **Never:** |
@@ -33,22 +33,30 @@ --- | ||
| 3. **Enhance Clarity**: Simplify code structure by: | ||
| 3. **Remove Slop**: Detect and remove these categories: | ||
| - Reducing unnecessary complexity and nesting | ||
| - Eliminating redundant code and abstractions | ||
| - Improving readability through clear variable and function names | ||
| | Category | Pattern | | ||
| |----------|---------| | ||
| | Language-behavior tests | Tests verifying built-in language features work as documented | | ||
| | Redundant type checks | Runtime checks for types TypeScript already enforces | | ||
| | Over-defensive handling | try/catch around code that cannot throw | | ||
| | Debug remnants | console.log, debugger, alert() left behind | | ||
| | Commented-out code | Dead code preserved in comments | | ||
| | Unused imports | Imports not referenced anywhere in file | | ||
| | Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) | | ||
| | Unnecessary intermediates | Variables used once, immediately after assignment | | ||
| 4. **Enhance Clarity**: Simplify code structure by: | ||
| - Reducing unnecessary nesting (early returns, guard clauses) | ||
| - Consolidating related logic | ||
| - Removing unnecessary comments that describe obvious code | ||
| - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions | ||
| - Choose clarity over brevity - explicit code is often better than overly compact code | ||
| - Avoiding nested ternary operators — prefer switch or if/else | ||
| - Choosing clarity over brevity — explicit code beats compact code | ||
| 4. **Maintain Balance**: Avoid over-simplification that could: | ||
| 5. **Maintain Balance**: Avoid over-simplification that could: | ||
| - Reduce code clarity or maintainability | ||
| - Create overly clever solutions that are hard to understand | ||
| - Combine too many concerns into single functions or components | ||
| - Remove helpful abstractions that improve code organization | ||
| - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) | ||
| - Make the code harder to debug or extend | ||
| 5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
| 6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
@@ -58,3 +66,3 @@ Your refinement process: | ||
| 1. Identify the recently modified code sections | ||
| 2. Analyze for opportunities to improve elegance and consistency | ||
| 2. Analyze for slop categories and clarity improvements | ||
| 3. Apply project-specific best practices and coding standards | ||
@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged | ||
| **Handle autonomously:** | ||
| - Naming improvements, dead code removal, nesting reduction | ||
| - Slop removal (all 8 categories) | ||
| - Naming improvements, nesting reduction | ||
| - Import sorting and organization | ||
| - Redundant abstraction elimination | ||
| - Comment cleanup (remove obvious, keep non-obvious) | ||
| - Comment cleanup (remove obvious, keep non-obvious) |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -33,22 +33,30 @@ --- | ||
| 3. **Enhance Clarity**: Simplify code structure by: | ||
| 3. **Remove Slop**: Detect and remove these categories: | ||
| - Reducing unnecessary complexity and nesting | ||
| - Eliminating redundant code and abstractions | ||
| - Improving readability through clear variable and function names | ||
| | Category | Pattern | | ||
| |----------|---------| | ||
| | Language-behavior tests | Tests verifying built-in language features work as documented | | ||
| | Redundant type checks | Runtime checks for types TypeScript already enforces | | ||
| | Over-defensive handling | try/catch around code that cannot throw | | ||
| | Debug remnants | console.log, debugger, alert() left behind | | ||
| | Commented-out code | Dead code preserved in comments | | ||
| | Unused imports | Imports not referenced anywhere in file | | ||
| | Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) | | ||
| | Unnecessary intermediates | Variables used once, immediately after assignment | | ||
| 4. **Enhance Clarity**: Simplify code structure by: | ||
| - Reducing unnecessary nesting (early returns, guard clauses) | ||
| - Consolidating related logic | ||
| - Removing unnecessary comments that describe obvious code | ||
| - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions | ||
| - Choose clarity over brevity - explicit code is often better than overly compact code | ||
| - Avoiding nested ternary operators — prefer switch or if/else | ||
| - Choosing clarity over brevity — explicit code beats compact code | ||
| 4. **Maintain Balance**: Avoid over-simplification that could: | ||
| 5. **Maintain Balance**: Avoid over-simplification that could: | ||
| - Reduce code clarity or maintainability | ||
| - Create overly clever solutions that are hard to understand | ||
| - Combine too many concerns into single functions or components | ||
| - Remove helpful abstractions that improve code organization | ||
| - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) | ||
| - Make the code harder to debug or extend | ||
| 5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
| 6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
@@ -58,3 +66,3 @@ Your refinement process: | ||
| 1. Identify the recently modified code sections | ||
| 2. Analyze for opportunities to improve elegance and consistency | ||
| 2. Analyze for slop categories and clarity improvements | ||
| 3. Apply project-specific best practices and coding standards | ||
@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged | ||
| **Handle autonomously:** | ||
| - Naming improvements, dead code removal, nesting reduction | ||
| - Slop removal (all 8 categories) | ||
| - Naming improvements, nesting reduction | ||
| - Import sorting and organization | ||
| - Redundant abstraction elimination | ||
| - Comment cleanup (remove obvious, keep non-obvious) | ||
| - Comment cleanup (remove obvious, keep non-obvious) |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -24,10 +24,12 @@ --- | ||
| 3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues. | ||
| 4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 6. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| 6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 7. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| ## Principles | ||
@@ -34,0 +36,0 @@ |
@@ -33,22 +33,30 @@ --- | ||
| 3. **Enhance Clarity**: Simplify code structure by: | ||
| 3. **Remove Slop**: Detect and remove these categories: | ||
| - Reducing unnecessary complexity and nesting | ||
| - Eliminating redundant code and abstractions | ||
| - Improving readability through clear variable and function names | ||
| | Category | Pattern | | ||
| |----------|---------| | ||
| | Language-behavior tests | Tests verifying built-in language features work as documented | | ||
| | Redundant type checks | Runtime checks for types TypeScript already enforces | | ||
| | Over-defensive handling | try/catch around code that cannot throw | | ||
| | Debug remnants | console.log, debugger, alert() left behind | | ||
| | Commented-out code | Dead code preserved in comments | | ||
| | Unused imports | Imports not referenced anywhere in file | | ||
| | Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) | | ||
| | Unnecessary intermediates | Variables used once, immediately after assignment | | ||
| 4. **Enhance Clarity**: Simplify code structure by: | ||
| - Reducing unnecessary nesting (early returns, guard clauses) | ||
| - Consolidating related logic | ||
| - Removing unnecessary comments that describe obvious code | ||
| - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions | ||
| - Choose clarity over brevity - explicit code is often better than overly compact code | ||
| - Avoiding nested ternary operators — prefer switch or if/else | ||
| - Choosing clarity over brevity — explicit code beats compact code | ||
| 4. **Maintain Balance**: Avoid over-simplification that could: | ||
| 5. **Maintain Balance**: Avoid over-simplification that could: | ||
| - Reduce code clarity or maintainability | ||
| - Create overly clever solutions that are hard to understand | ||
| - Combine too many concerns into single functions or components | ||
| - Remove helpful abstractions that improve code organization | ||
| - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) | ||
| - Make the code harder to debug or extend | ||
| 5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
| 6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
@@ -58,3 +66,3 @@ Your refinement process: | ||
| 1. Identify the recently modified code sections | ||
| 2. Analyze for opportunities to improve elegance and consistency | ||
| 2. Analyze for slop categories and clarity improvements | ||
| 3. Apply project-specific best practices and coding standards | ||
@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged | ||
| **Handle autonomously:** | ||
| - Naming improvements, dead code removal, nesting reduction | ||
| - Slop removal (all 8 categories) | ||
| - Naming improvements, nesting reduction | ||
| - Import sorting and organization | ||
| - Redundant abstraction elimination | ||
| - Comment cleanup (remove obvious, keep non-obvious) | ||
| - Comment cleanup (remove obvious, keep non-obvious) |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.7.0", | ||
| "version": "1.8.0", | ||
| "homepage": "https://github.com/dean0x/devflow", | ||
@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow", |
+31
-1
@@ -27,3 +27,3 @@ # DevFlow | ||
| - **Parallel debugging** — competing hypotheses investigated simultaneously | ||
| - **Version notifications** — statusline shows an update badge when a newer version is available | ||
| - **Version notifications** — HUD shows `✦ Devflow vX.Y.Z · update: npx devflow-kit init` when a newer version is available | ||
| - **35 quality skills** — 9 auto-activating core, 8 optional language/ecosystem, plus specialized review, agent, and orchestration skills | ||
@@ -252,4 +252,34 @@ | ||
| | `--memory` / `--no-memory` | Enable/disable working memory (default: on) | | ||
| | `--hud-only` | Install only the HUD (no plugins, hooks, or extras) | | ||
| | `--no-hud` | Disable HUD status line | | ||
| | `--verbose` | Show detailed output | | ||
| ### HUD Options | ||
| | Command | Description | | ||
| |---------|-------------| | ||
| | `npx devflow-kit hud --status` | Show current HUD config | | ||
| | `npx devflow-kit hud --enable` | Enable HUD | | ||
| | `npx devflow-kit hud --disable` | Disable HUD (version notifications still appear) | | ||
| | `npx devflow-kit hud --detail` | Show tool/agent descriptions | | ||
| | `npx devflow-kit hud --no-detail` | Hide tool/agent descriptions | | ||
| ### Skill Shadowing | ||
| Override any DevFlow skill with your own version. Shadowed skills survive `devflow init` — they won't be overwritten on reinstall or upgrade. | ||
| ```bash | ||
| # Create a personal override (copies current version as reference) | ||
| npx devflow-kit skills shadow core-patterns | ||
| # Edit your override | ||
| vim ~/.claude/skills/core-patterns/SKILL.md | ||
| # List all overrides | ||
| npx devflow-kit skills list-shadowed | ||
| # Remove override (next init restores DevFlow's version) | ||
| npx devflow-kit skills unshadow core-patterns | ||
| ``` | ||
| ### Uninstall Options | ||
@@ -256,0 +286,0 @@ |
@@ -24,10 +24,12 @@ --- | ||
| 3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues. | ||
| 4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found. | ||
| 5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward. | ||
| 6. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| 6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues". | ||
| 7. **Report status**: Return structured report with pillar evaluations and changes made. | ||
| ## Principles | ||
@@ -34,0 +36,0 @@ |
@@ -24,6 +24,16 @@ --- | ||
| 2. **Review implementation**: Read FILES_CHANGED to understand what was built | ||
| 3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 4. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 5. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| 3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth. | ||
| 4. **Check artifact depth**: Classify each deliverable using this scale: | ||
| | Depth | Meaning | Example | | ||
| |-------|---------|---------| | ||
| | Exists | File/function created | Route file exists | | ||
| | Substantive | Contains real logic | Route has validation + DB call | | ||
| | Wired | Connected to running app | Route registered, imported, reachable | | ||
| Flag anything at "Exists" without reaching "Wired" as `incomplete`. | ||
| 5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met | ||
| 6. **Check scope**: Identify out-of-scope additions not justified by design improvements | ||
| 7. **Report misalignments**: Document issues with sufficient detail for Coder to fix | ||
| ## Principles | ||
@@ -55,2 +65,7 @@ | ||
| ### Artifact Depth | ||
| | Deliverable | Exists | Substantive | Wired | Status | | ||
| |-------------|--------|-------------|-------|--------| | ||
| | {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub | | ||
| ### Misalignments Found (if MISALIGNED) | ||
@@ -64,2 +79,3 @@ | ||
| | intent_drift | {how intent drifted} | {file paths} | {how to realign} | | ||
| | stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} | | ||
@@ -69,13 +85,9 @@ ### Scope Check | ||
| - Justification: {if additions found, are they justified design improvements?} | ||
| ### Re-verification (if applicable) | ||
| | Previously Failed | Status | Notes | | ||
| |-------------------|--------|-------| | ||
| | {item} | RESOLVED/STILL_FAILING | {details} | | ||
| ``` | ||
| ## Misalignment Types | ||
| | Type | Description | Example | | ||
| |------|-------------|---------| | ||
| | `missing` | Functionality in plan not implemented | "Login validation not implemented" | | ||
| | `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" | | ||
| | `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" | | ||
| | `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" | | ||
| ## Boundaries | ||
@@ -88,2 +100,3 @@ | ||
| - Intent drift | ||
| - Stubs or placeholders passing as real implementations | ||
@@ -95,2 +108,3 @@ **Report as ALIGNED:** | ||
| - Implementation matches original intent | ||
| - All deliverables reach "Wired" depth | ||
@@ -97,0 +111,0 @@ **Never:** |
@@ -33,22 +33,30 @@ --- | ||
| 3. **Enhance Clarity**: Simplify code structure by: | ||
| 3. **Remove Slop**: Detect and remove these categories: | ||
| - Reducing unnecessary complexity and nesting | ||
| - Eliminating redundant code and abstractions | ||
| - Improving readability through clear variable and function names | ||
| | Category | Pattern | | ||
| |----------|---------| | ||
| | Language-behavior tests | Tests verifying built-in language features work as documented | | ||
| | Redundant type checks | Runtime checks for types TypeScript already enforces | | ||
| | Over-defensive handling | try/catch around code that cannot throw | | ||
| | Debug remnants | console.log, debugger, alert() left behind | | ||
| | Commented-out code | Dead code preserved in comments | | ||
| | Unused imports | Imports not referenced anywhere in file | | ||
| | Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) | | ||
| | Unnecessary intermediates | Variables used once, immediately after assignment | | ||
| 4. **Enhance Clarity**: Simplify code structure by: | ||
| - Reducing unnecessary nesting (early returns, guard clauses) | ||
| - Consolidating related logic | ||
| - Removing unnecessary comments that describe obvious code | ||
| - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions | ||
| - Choose clarity over brevity - explicit code is often better than overly compact code | ||
| - Avoiding nested ternary operators — prefer switch or if/else | ||
| - Choosing clarity over brevity — explicit code beats compact code | ||
| 4. **Maintain Balance**: Avoid over-simplification that could: | ||
| 5. **Maintain Balance**: Avoid over-simplification that could: | ||
| - Reduce code clarity or maintainability | ||
| - Create overly clever solutions that are hard to understand | ||
| - Combine too many concerns into single functions or components | ||
| - Remove helpful abstractions that improve code organization | ||
| - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) | ||
| - Make the code harder to debug or extend | ||
| 5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
| 6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. | ||
@@ -58,3 +66,3 @@ Your refinement process: | ||
| 1. Identify the recently modified code sections | ||
| 2. Analyze for opportunities to improve elegance and consistency | ||
| 2. Analyze for slop categories and clarity improvements | ||
| 3. Apply project-specific best practices and coding standards | ||
@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged | ||
| **Handle autonomously:** | ||
| - Naming improvements, dead code removal, nesting reduction | ||
| - Slop removal (all 8 categories) | ||
| - Naming improvements, nesting reduction | ||
| - Import sorting and organization | ||
| - Redundant abstraction elimination | ||
| - Comment cleanup (remove obvious, keep non-obvious) | ||
| - Comment cleanup (remove obvious, keep non-obvious) |
| { | ||
| "statusLine": { | ||
| "type": "command", | ||
| "command": "${DEVFLOW_DIR}/scripts/statusline.sh" | ||
| "command": "${DEVFLOW_DIR}/scripts/hud.sh" | ||
| }, | ||
@@ -6,0 +6,0 @@ "hooks": { |
Network access
Supply chain riskThis module accesses the network.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
2685840
8.13%579
36.88%7487
78.39%306
10.87%55
139.13%8
33.33%