# Coding Conventions **Analysis Date:** 2026-03-27 ## Language & Runtime **Primary Language:** JavaScript (CommonJS) - Node.js environment (no TypeScript transpilation) - All hooks use `.js` extension with shebang: `#!/usr/bin/env node` **Module System:** - CommonJS (`require()` only, no ES6 imports) - Node.js built-in modules: `fs`, `path`, `os`, `child_process` - No external npm dependencies in hook files ## Naming Patterns **Files:** - Kebab-case: `gsd-hook-name.js` - Pattern: `gsd-` prefix + feature name + `.js` extension - Examples: `gsd-prompt-guard.js`, `gsd-context-monitor.js`, `gsd-statusline.js` **Functions:** - camelCase: `detectConfigDir()`, `clearTimeout()`, `writeFileSync()` - Single-letter shorthand acceptable for simple operations: `f` for file in loops **Constants:** - UPPER_SNAKE_CASE for immutable values: `WARNING_THRESHOLD`, `CRITICAL_THRESHOLD`, `STALE_SECONDS`, `DEBOUNCE_CALLS` - Inline comments after constants for clarity: `const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%` **Variables:** - camelCase: `input`, `stdinTimeout`, `data`, `filePath`, `findings`, `configPath` - Descriptive names: `remaining_percentage`, `callsSinceWarn`, `hookEventName` - Single-letter loop variables: `f`, `e` (for errors), `p` (for patterns) **Types/Objects:** - Object keys use snake_case: `tool_name`, `tool_input`, `file_path`, `hook_version`, `installed_version` - Array items plural: `staleHooks`, `findings`, `allowedPatterns`, `files`, `hookFiles` ## Code Style **Formatting:** - No linter configured (no eslintrc, prettier, or biome config files found) - Line length: typically 80-120 characters, no strict enforced limit observed - Indentation: 2 spaces consistently across all files **Comments:** - Single-line comments: `// comment` - Multi-line blocks: Multiple `//` lines stacked (no `/* */` blocks found) - Header comments with metadata: Version, purpose, triggers, behavior - Pattern: `// gsd-hook-version: X.Y.Z` as first comment after shebang - Purpose explanation follows immediately **String Formatting:** - Single quotes for regular strings: `'utf8'`, `'end'` - Template literals for interpolation: `` `${variable}` `` - Backticks for paths and code examples in comments: `` `${filePath}` `` ## Error Handling **Try-Catch Pattern:** - All file I/O and JSON parsing wrapped in try-catch - Silent failures preferred: catch blocks often empty or call `process.exit(0)` - Examples from codebase: - `try { const data = JSON.parse(input); } catch { process.exit(0); }` - `try { ... } catch (e) { // Silent fail on parse errors }` **Graceful Degradation:** - Never block operations with errors - Timeout guards prevent hanging: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);` - File existence checks before operations: `if (fs.existsSync(configPath)) { ... }` - Return early on missing critical data: `if (!sessionId) { process.exit(0); }` **Error Recovery:** - Corrupted files reset to defaults: `catch (e) { warnData = { callsSinceWarn: 0, lastLevel: null }; }` - Optional chain operators for safe access: `data.tool_input?.file_path || ''` ## Type Coercion & Checks **Null/Undefined Handling:** - Null coalescing default values: `data.session_id || ''`, `data.cwd || process.cwd()` - Explicit null checks: `if (remaining != null)` (distinguishes null from undefined) - Undefined/null fallbacks: `|| 'unknown'`, `|| []`, `|| {}` **Truthiness Checks:** - Explicit boolean comparisons: `if (config.hooks?.workflow_guard) { ... }` - Array length checks: `if (files.length > 0) { ... }`, `if (findings.length === 0) { ... }` **Number Conversions:** - Explicit parsing: `Math.floor(Date.now() / 1000)`, `Math.round(100 - usableRemaining)` - Clamping with Math: `Math.max(0, value)`, `Math.min(100, value)` ## Imports & Requires **Order (when present):** 1. Node.js built-in modules: `fs`, `path`, `os`, `child_process` 2. Destructured imports grouped: `const { spawn } = require('child_process');` 3. No external package requires in hooks **Require Pattern:** ```javascript const fs = require('fs'); const path = require('path'); const os = require('os'); const { spawn } = require('child_process'); ``` ## Object & Array Patterns **Object Creation:** - Literal syntax: `{ hookEventName: 'PreToolUse', additionalContext: message }` - Computed properties from template literals: `` { [key]: value } `` (not observed, uses direct literals) - Spread operators: Not used in codebase **Array Operations:** - `.filter()`: `files.filter(f => f.startsWith(session))` - `.map()`: `files.map(f => ({ name: f, mtime: fs.statSync(...) }))` - `.find()`: `todos.find(t => t.status === 'in_progress')` - `.some()`: `allowedPatterns.some(p => p.test(filePath))` - Arrow functions for callbacks: `chunk => input += chunk` **Sorting & Ordering:** - Reverse chronological: `files.sort((a, b) => b.mtime - a.mtime)` - File system operations ordered first, then filtering ## Output Patterns **JSON Output:** - All hook output is JSON: `process.stdout.write(JSON.stringify(output));` - Output structure includes `hookSpecificOutput` wrapper: ```javascript const output = { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: message } }; ``` **Process Exit:** - Success: `process.exit(0)` or implicit exit after writing output - Error: `process.exit(0)` (never `process.exit(1)` to avoid breaking workflows) - Cleanup: `clearTimeout()` called before operations ## File I/O Patterns **Synchronous Operations:** - `fs.readFileSync(path, 'utf8')` for reads - `fs.writeFileSync(path, JSON.stringify(data))` for writes - `fs.existsSync(path)` for checks - `fs.readdirSync(path)` for directory listing - `fs.statSync(path).mtime` for file metadata **Path Operations:** - `path.join()` for path concatenation: `path.join(cwd, '.planning', 'config.json')` - `path.basename(dir)` for extracting final component - `path.dirname()` for parent directory ## Regular Expressions **Pattern Definition:** - Captured in constants array at file top: `const INJECTION_PATTERNS = [ /pattern1/i, /pattern2/i ]` - Case-insensitive flag commonly used: `/pattern/i` - Multiline patterns use raw strings: `/[\u200B-\u200F]/` for Unicode detection **Pattern Testing:** - `.test()` method for boolean check: `if (pattern.test(content))` - `.match()` for capture groups: `const versionMatch = content.match(/\\/\\/ gsd-hook-version:\\s*(.+)/)` - `.source` property for pattern string: `findings.push(pattern.source)` ## No Additional Frameworks **Testing:** Not detected - no test files exist **Build Tools:** Not detected - runs directly as Node.js scripts **Linting/Formatting:** Not detected - no .eslintrc, .prettierrc, or similar configs --- *Convention analysis: 2026-03-27*