Commit 8c129be2b15a

Vincent Demeester <vincent@sbr.pm>
2025-12-04 14:27:14
chore: remove typescript hooks…
… and tidy a bit the Makefile targets. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 80771ae
dots/.config/claude/hooks/lib/claude-paths.ts
@@ -1,69 +0,0 @@
-#!/usr/bin/env bun
-
-import { homedir } from 'os';
-import { resolve, join } from 'path';
-import { existsSync } from 'fs';
-
-/**
- * Claude Code infrastructure paths
- *
- * Defaults to ~/.claude but can be overridden with CLAUDE_DIR env var
- */
-export const CLAUDE_DIR = process.env.CLAUDE_DIR
-  ? resolve(process.env.CLAUDE_DIR)
-  : resolve(homedir(), '.claude');
-
-export const HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
-export const SKILLS_DIR = join(CLAUDE_DIR, 'skills');
-export const AGENTS_DIR = join(CLAUDE_DIR, 'agents');
-export const HISTORY_DIR = join(CLAUDE_DIR, 'history');
-
-/**
- * Validate Claude Code directory structure
- * Called automatically on import
- */
-function validateClaudeStructure(): void {
-  if (!existsSync(CLAUDE_DIR)) {
-    console.error(`❌ CLAUDE_DIR does not exist: ${CLAUDE_DIR}`);
-    console.error(`   Expected ~/.claude or set CLAUDE_DIR environment variable`);
-    process.exit(1);
-  }
-
-  if (!existsSync(HOOKS_DIR)) {
-    console.error(`⚠️  Claude hooks directory not found: ${HOOKS_DIR}`);
-    console.error(`   This may be expected if hooks haven't been set up yet`);
-    // Don't exit - this is okay for initial setup
-  }
-}
-
-// Validate on import
-validateClaudeStructure();
-
-/**
- * Get a history file path with proper year-month organization
- * @param subdir - History subdirectory (sessions, learnings, research, etc.)
- * @param filename - File name
- * @returns Full path to history file
- */
-export function getHistoryFilePath(subdir: string, filename: string): string {
-  const now = new Date();
-  const year = now.getFullYear();
-  const month = String(now.getMonth() + 1).padStart(2, '0');
-
-  return join(HISTORY_DIR, subdir, `${year}-${month}`, filename);
-}
-
-/**
- * Get current timestamp in YYYY-MM-DD-HHMMSS format
- */
-export function getTimestamp(): string {
-  const now = new Date();
-  const year = now.getFullYear();
-  const month = String(now.getMonth() + 1).padStart(2, '0');
-  const day = String(now.getDate()).padStart(2, '0');
-  const hours = String(now.getHours()).padStart(2, '0');
-  const minutes = String(now.getMinutes()).padStart(2, '0');
-  const seconds = String(now.getSeconds()).padStart(2, '0');
-
-  return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
-}
dots/.config/claude/hooks/capture-tool-output.ts
@@ -1,77 +0,0 @@
-#!/usr/bin/env bun
-
-/**
- * PostToolUse Hook - Captures tool outputs for history tracking
- *
- * Automatically logs interesting tool executions to daily JSONL files
- * for later analysis and session reconstruction.
- *
- * Setup:
- * Add to PostToolUse in settings.json:
- * "PostToolUse": ["bun /home/vincent/.claude/hooks/capture-tool-output.ts"]
- */
-
-import { appendFileSync, mkdirSync, existsSync } from 'fs';
-import { join } from 'path';
-import { CLAUDE_DIR } from './lib/claude-paths';
-
-interface ToolUseData {
-  tool_name: string;
-  tool_input: Record<string, any>;
-  tool_response: Record<string, any>;
-  conversation_id: string;
-  timestamp: string;
-}
-
-// Configuration - which tools to capture
-const INTERESTING_TOOLS = ['Bash', 'Edit', 'Write', 'Read', 'Task', 'NotebookEdit', 'Skill', 'SlashCommand'];
-
-async function main() {
-  try {
-    // Read input from stdin
-    const input = await Bun.stdin.text();
-    if (!input || input.trim() === '') {
-      process.exit(0);
-    }
-
-    const data: ToolUseData = JSON.parse(input);
-
-    // Only capture interesting tools
-    if (!INTERESTING_TOOLS.includes(data.tool_name)) {
-      process.exit(0);
-    }
-
-    // Get today's date for organization
-    const now = new Date();
-    const today = now.toISOString().split('T')[0]; // YYYY-MM-DD
-    const yearMonth = today.substring(0, 7); // YYYY-MM
-
-    // Ensure capture directory exists
-    const dateDir = join(CLAUDE_DIR, 'history', 'tool-outputs', yearMonth);
-    if (!existsSync(dateDir)) {
-      mkdirSync(dateDir, { recursive: true });
-    }
-
-    // Format output as JSONL (one JSON object per line)
-    const captureFile = join(dateDir, `${today}_tool-outputs.jsonl`);
-    const captureEntry = JSON.stringify({
-      timestamp: data.timestamp || now.toISOString(),
-      tool: data.tool_name,
-      input: data.tool_input,
-      output: data.tool_response,
-      session: data.conversation_id
-    }) + '\n';
-
-    // Append to daily log
-    appendFileSync(captureFile, captureEntry);
-
-    // Exit successfully (code 0 = continue normally)
-    process.exit(0);
-  } catch (error) {
-    // Silent failure - don't disrupt workflow
-    console.error(`[capture-tool-output] Error: ${error}`);
-    process.exit(0);
-  }
-}
-
-main();
dots/.config/claude/hooks/initialize-session.ts
@@ -1,105 +0,0 @@
-#!/usr/bin/env bun
-
-/**
- * initialize-session.ts
- *
- * Session initialization hook that runs at the start of every Claude Code session.
- *
- * What it does:
- * - Checks if this is a subagent session (skips for subagents)
- * - Sets initial terminal tab title
- * - Logs session start
- *
- * Setup:
- * Add to SessionStart in settings.json:
- * "SessionStart": ["bun /home/vincent/.claude/hooks/initialize-session.ts"]
- */
-
-import { existsSync, mkdirSync, appendFileSync } from 'fs';
-import { join } from 'path';
-import { tmpdir } from 'os';
-import { CLAUDE_DIR, getHistoryFilePath, getTimestamp } from './lib/claude-paths';
-
-// Debounce duration in milliseconds (prevents duplicate SessionStart events)
-const DEBOUNCE_MS = 2000;
-const LOCKFILE = join(tmpdir(), 'claude-session-start.lock');
-
-/**
- * Check if we're within the debounce window to prevent duplicate notifications
- */
-function shouldDebounce(): boolean {
-  try {
-    if (existsSync(LOCKFILE)) {
-      const lockContent = Bun.file(LOCKFILE).text();
-      const lockTime = parseInt(await lockContent, 10);
-      const now = Date.now();
-
-      if (now - lockTime < DEBOUNCE_MS) {
-        // Within debounce window, skip this notification
-        return true;
-      }
-    }
-
-    // Update lockfile with current timestamp
-    await Bun.write(LOCKFILE, Date.now().toString());
-    return false;
-  } catch (error) {
-    // If any error, just proceed (don't break session start)
-    try {
-      await Bun.write(LOCKFILE, Date.now().toString());
-    } catch {}
-    return false;
-  }
-}
-
-async function main() {
-  try {
-    // Check if this is a subagent session - if so, exit silently
-    const claudeProjectDir = process.env.CLAUDE_PROJECT_DIR || '';
-    const isSubagent = claudeProjectDir.includes('/.claude/agents/') ||
-                      process.env.CLAUDE_AGENT_TYPE !== undefined;
-
-    if (isSubagent) {
-      // This is a subagent session - exit silently without notification
-      console.error('🤖 Subagent session detected - skipping session initialization');
-      process.exit(0);
-    }
-
-    // Check debounce to prevent duplicate notifications
-    if (await shouldDebounce()) {
-      console.error('🔇 Debouncing duplicate SessionStart event');
-      process.exit(0);
-    }
-
-    // Set initial tab title
-    const tabTitle = 'Claude Ready';
-    process.stderr.write(`\x1b]0;${tabTitle}\x07`);
-    process.stderr.write(`\x1b]2;${tabTitle}\x07`);
-    process.stderr.write(`\x1b]30;${tabTitle}\x07`);
-    console.error(`📍 Session initialized: "${tabTitle}"`);
-
-    // Log session start to history (optional)
-    const timestamp = getTimestamp();
-    const logDir = join(CLAUDE_DIR, 'history', 'sessions', `${timestamp.substring(0, 7)}`);
-
-    if (!existsSync(logDir)) {
-      mkdirSync(logDir, { recursive: true });
-    }
-
-    const logEntry = `${new Date().toISOString()} - Session started\n`;
-    const logFile = join(logDir, `${timestamp.substring(0, 10)}_session-log.txt`);
-
-    try {
-      appendFileSync(logFile, logEntry);
-    } catch (error) {
-      // Silent failure - don't break session start for logging issues
-    }
-
-    process.exit(0);
-  } catch (error) {
-    console.error('SessionStart hook error:', error);
-    process.exit(1);
-  }
-}
-
-main();
dots/.config/claude/hooks/README.md
@@ -1,200 +0,0 @@
-# Claude Code Hooks
-
-This directory contains hooks for Claude Code that automate various tasks and capture session information.
-
-Adapted from [Personal AI Infrastructure](https://github.com/danielmiessler/Personal_AI_Infrastructure).
-
-## Installed Hooks
-
-### initialize-session.ts
-
-**Purpose**: Session initialization
-
-**Triggers**: SessionStart event
-
-**What it does**:
-- Detects and skips subagent sessions
-- Sets terminal tab title to "Claude Ready"
-- Logs session start to history
-- Implements debouncing to prevent duplicate triggers
-
-**Setup**:
-```json
-{
-  "hooks": {
-    "SessionStart": ["bun ~/.config/claude/hooks/initialize-session.ts"]
-  }
-}
-```
-
-### capture-tool-output.ts
-
-**Purpose**: Tool execution logging
-
-**Triggers**: PostToolUse event
-
-**What it does**:
-- Captures outputs from interesting tools (Bash, Edit, Write, Read, Task, etc.)
-- Logs to JSONL files organized by year-month
-- Stores in `~/.config/claude/history/tool-outputs/YYYY-MM/YYYY-MM-DD_tool-outputs.jsonl`
-- Silent failure - doesn't disrupt workflow
-
-**Setup**:
-```json
-{
-  "hooks": {
-    "PostToolUse": ["bun ~/.config/claude/hooks/capture-tool-output.ts"]
-  }
-}
-```
-
-**Captured tools**:
-- Bash
-- Edit, Write, Read
-- Task
-- NotebookEdit
-- Skill, SlashCommand
-
-### validate-docs.ts
-
-**Purpose**: Documentation link validation
-
-**Triggers**: Manual or pre-commit
-
-**What it does**:
-- Scans all markdown files for internal links
-- Checks if linked files exist
-- Reports broken links with file:line numbers
-- Exit code 0 if valid, 1 if broken links found
-
-**Usage**:
-```bash
-# Run manually
-bun ~/.config/claude/hooks/validate-docs.ts
-
-# Add to pre-commit (optional)
-```
-
-## Skipped Hooks
-
-The following hooks from PAI were **not imported** as they require more setup or are not immediately needed:
-
-### capture-session-summary.ts
-**Reason**: Complex - requires parsing conversation output files and generating markdown summaries. Can be added later if needed.
-
-### capture-all-events.ts
-**Reason**: Very comprehensive event logging with agent metadata extraction. More complex than capture-tool-output. Can add if detailed event tracking is needed.
-
-### self-test.ts
-**Reason**: Validates PAI-specific directory structure and configurations. Would need significant adaptation for our setup.
-
-### validate-protected.ts
-**Reason**: Requires `.pai-protected.json` manifest defining protected file patterns. Can add later with custom protection rules.
-
-## Hook Architecture
-
-### Directory Structure
-
-```
-~/.config/claude/hooks/
-├── README.md                    # This file
-├── lib/
-│   └── claude-paths.ts         # Path utilities
-├── initialize-session.ts       # Session startup
-├── capture-tool-output.ts      # Tool logging
-└── validate-docs.ts            # Link validation
-```
-
-### Helper Library: lib/claude-paths.ts
-
-Provides:
-- `CLAUDE_DIR`: Base directory (~/.config/claude)
-- `HOOKS_DIR`, `SKILLS_DIR`, `AGENTS_DIR`, `HISTORY_DIR`: Subdirectories
-- `getHistoryFilePath(subdir, filename)`: Generate history file paths
-- `getTimestamp()`: Get formatted timestamp
-- Path validation on import
-
-**Note**: For backward compatibility, `~/.claude/` is symlinked to `~/.config/claude/`.
-
-## Configuration
-
-Hooks are configured in Claude Code settings (`.config/claude/settings.json` or `.config/claude/settings.local.json`):
-
-```json
-{
-  "hooks": {
-    "SessionStart": ["bun ~/.config/claude/hooks/initialize-session.ts"],
-    "PostToolUse": ["bun ~/.config/claude/hooks/capture-tool-output.ts"]
-  }
-}
-```
-
-Available hook events:
-- `SessionStart`: Session begins
-- `SessionEnd`: Session ends
-- `PreToolUse`: Before tool execution
-- `PostToolUse`: After tool execution
-- `UserPromptSubmit`: After user submits prompt
-- `Stop`: Session stopped
-- `SubagentStop`: Subagent stopped
-- `PreCompact`: Before context compaction
-- `Notification`: System notification
-
-## Requirements
-
-- **Bun**: All hooks use `#!/usr/bin/env bun` shebang
-- Install: `curl -fsSL https://bun.sh/install | bash`
-- Already installed on kyushu system
-
-## History Logging
-
-Hooks that capture data store it in `~/.config/claude/history/`:
-
-```
-~/.config/claude/history/
-├── sessions/
-│   └── YYYY-MM/
-│       └── YYYY-MM-DD_session-log.txt
-└── tool-outputs/
-    └── YYYY-MM/
-        └── YYYY-MM-DD_tool-outputs.jsonl
-```
-
-This integrates with the history system documented in `.config/claude/skills/CORE/history-system.md`.
-
-## Adding More Hooks
-
-To add additional hooks:
-
-1. Create the hook file in `~/.config/claude/hooks/`
-2. Add shebang: `#!/usr/bin/env bun`
-3. Make it executable: `chmod +x hook-name.ts`
-4. Use `lib/claude-paths.ts` for paths
-5. Add to settings.json under appropriate event
-6. Test manually before enabling
-
-## Troubleshooting
-
-**Hook not running:**
-- Check settings.json syntax
-- Verify file is executable (`ls -la ~/.config/claude/hooks/`)
-- Check hook output in stderr
-- Verify Bun is installed: `bun --version`
-
-**Permission errors:**
-- Ensure directories exist: `mkdir -p ~/.config/claude/history/{sessions,tool-outputs}`
-- Check write permissions
-
-**Debugging:**
-- Hooks write to stderr - check terminal output
-- Add debug logging: `console.error('[hook-name] Debug message')`
-- Run manually: `bun ~/.config/claude/hooks/hook-name.ts`
-
-## Future Enhancements
-
-Consider adding:
-- Session summary generation (port capture-session-summary.ts)
-- Protected file validation (port validate-protected.ts)
-- Custom event logging for specific workflows
-- Integration with external notification systems
-- Automatic documentation generation
dots/.config/claude/hooks/validate-docs.ts
@@ -1,155 +0,0 @@
-#!/usr/bin/env bun
-
-/**
- * Documentation Link Validator
- *
- * Validates that all internal markdown links point to existing files.
- * Useful as a pre-commit hook to prevent broken documentation.
- *
- * Usage:
- *   bun run ~/.claude/hooks/validate-docs.ts
- *
- * Exit codes:
- *   0 - All links valid
- *   1 - Broken links found
- */
-
-import { readFileSync, existsSync } from 'fs';
-import { join, dirname, resolve } from 'path';
-import { Glob } from 'bun';
-
-// ANSI color codes
-const colors = {
-  reset: '\x1b[0m',
-  red: '\x1b[31m',
-  green: '\x1b[32m',
-  yellow: '\x1b[33m',
-  cyan: '\x1b[36m',
-};
-
-interface BrokenLink {
-  file: string;
-  link: string;
-  target: string;
-  line: number;
-}
-
-/**
- * Extract markdown links from content
- */
-function extractLinks(content: string): { link: string; line: number }[] {
-  const links: { link: string; line: number }[] = [];
-  const lines = content.split('\n');
-
-  // Match [text](path) style links
-  const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
-
-  lines.forEach((line, index) => {
-    let match;
-    while ((match = linkRegex.exec(line)) !== null) {
-      const link = match[2];
-
-      // Skip external URLs, anchors, and mailto links
-      if (link.startsWith('http://') ||
-          link.startsWith('https://') ||
-          link.startsWith('#') ||
-          link.startsWith('mailto:')) {
-        continue;
-      }
-
-      links.push({ link, line: index + 1 });
-    }
-  });
-
-  return links;
-}
-
-/**
- * Resolve a link path relative to the file
- */
-function resolveLink(fromFile: string, linkPath: string, baseDir: string): string {
-  // Remove anchor if present
-  const pathWithoutAnchor = linkPath.split('#')[0];
-
-  // If it starts with ~, expand to home directory
-  if (pathWithoutAnchor.startsWith('~/')) {
-    return resolve(process.env.HOME || '', pathWithoutAnchor.substring(2));
-  }
-
-  // If it's absolute, use as-is
-  if (pathWithoutAnchor.startsWith('/')) {
-    return pathWithoutAnchor;
-  }
-
-  // Otherwise, resolve relative to the file's directory
-  const fileDir = dirname(fromFile);
-  return resolve(fileDir, pathWithoutAnchor);
-}
-
-/**
- * Validate markdown files in a directory
- */
-function validateDocs(baseDir: string): BrokenLink[] {
-  const brokenLinks: BrokenLink[] = [];
-
-  // Find all markdown files
-  const glob = new Glob('**/*.md');
-
-  for (const file of glob.scanSync({ cwd: baseDir, absolute: false })) {
-    const filePath = join(baseDir, file);
-
-    // Skip node_modules and hidden directories
-    if (filePath.includes('node_modules') || filePath.includes('/.')) {
-      continue;
-    }
-
-    try {
-      const content = readFileSync(filePath, 'utf-8');
-      const links = extractLinks(content);
-
-      for (const { link, line } of links) {
-        const targetPath = resolveLink(filePath, link, baseDir);
-
-        // Check if target exists
-        if (!existsSync(targetPath)) {
-          brokenLinks.push({
-            file: file,
-            link: link,
-            target: targetPath,
-            line: line,
-          });
-        }
-      }
-    } catch (error) {
-      console.error(`${colors.yellow}Warning: Could not read ${file}${colors.reset}`);
-    }
-  }
-
-  return brokenLinks;
-}
-
-function main(): number {
-  const baseDir = process.cwd();
-
-  console.log(`\n${colors.cyan}🔍 Documentation Link Validator${colors.reset}`);
-  console.log(`${colors.cyan}   Base directory: ${baseDir}${colors.reset}\n`);
-
-  const brokenLinks = validateDocs(baseDir);
-
-  if (brokenLinks.length > 0) {
-    console.log(`\n${colors.red}❌ Found ${brokenLinks.length} broken link(s):${colors.reset}\n`);
-
-    for (const { file, link, line } of brokenLinks) {
-      console.log(`  ${colors.yellow}${file}:${line}${colors.reset}`);
-      console.log(`    → ${colors.red}${link}${colors.reset} (not found)\n`);
-    }
-
-    console.log(`\n${colors.red}Documentation validation failed. Please fix the broken links.${colors.reset}\n`);
-    return 1;
-  }
-
-  console.log(`${colors.green}✅ All documentation links are valid${colors.reset}\n`);
-  return 0;
-}
-
-process.exit(main());
dots/Makefile
@@ -22,22 +22,11 @@ niri : ~/.config/niri/config.kdl
 all += claude-skills
 claude-skills : ~/.config/claude/skills
 
-all += claude-agents
+all += claude-agents claude-settings claude-plugins claude-statusline claude-compat
 claude-agents : ~/.config/claude/agents
-
-all += claude-hooks
-claude-hooks : ~/.config/claude/hooks
-
-all += claude-settings
 claude-settings : ~/.config/claude/settings.json
-
-all += claude-plugins
 claude-plugins : ~/.config/claude/plugins/session-manager
-
-all += claude-statusline
 claude-statusline : ~/.config/claude/statusline.sh
-
-all += claude-compat
 claude-compat : ~/.claude
 
 # Backward compatibility: symlink ~/.claude to ~/.config/claude