180 lines
6.6 KiB
Markdown
180 lines
6.6 KiB
Markdown
# 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*
|