8.1 KiB
Testing Patterns
Analysis Date: 2026-03-27
Test Framework
Status: Not detected
- No test files (*.test.js, *.spec.js) found in codebase
- No test runner configured (no jest.config.js, vitest.config.js, or mocha configuration)
- No test dependencies listed in package.json files
Code Context:
- The codebase consists of 5 Node.js hook scripts (579 total lines across
.claude/hooks/) - Each hook is a standalone CLI tool that reads JSON from stdin and outputs JSON to stdout
- Hooks are event-driven (SessionStart, PreToolUse, PostToolUse, AfterTool lifecycle events)
- No application code beyond these hooks exists in the repository
Script Type & Testing Approach
Current Architecture:
Each hook file (gsd-*.js in /home/ys/family-repo/AgenticCode/.claude/hooks/) follows the same structural pattern:
- Shebang:
#!/usr/bin/env node - Node.js built-in modules only (fs, path, os, child_process)
- JSON stdin → processing → JSON stdout
- Silent failure on errors (timeout guards, try-catch with exit(0))
Hook Files:
gsd-prompt-guard.js(96 lines) - Detects prompt injection patterns in written filesgsd-statusline.js(119 lines) - Renders context usage and task statusgsd-context-monitor.js(156 lines) - Warns when context window is lowgsd-workflow-guard.js(94 lines) - Advises on workflow compliancegsd-check-update.js(114 lines) - Checks for GSD updates in background
Manual Testing Indicators
Integration Points (evidence of real-world testing):
- Comments referencing specific issues:
See #775,See #1162,See #870,See #884 - Platform-specific workarounds:
windowsHide: truefor child_process to prevent console flash on Windows - Timeout guards:
const stdinTimeout = setTimeout(() => process.exit(0), 3000);prevents hanging on pipe issues - Git Bash compatibility: explicit handling of stdin timeout on Windows Git Bash
Behavioral Validation Patterns:
- Config file validation: reads
.planning/config.json, catches parse errors gracefully - File existence checks before operations:
if (fs.existsSync(filePath)) - Stale data detection:
if ((now - metrics.timestamp) > STALE_SECONDS) - Severity escalation tracking: debounce counter resets on warning level change
Input Validation
JSON Parsing with Error Handling:
All hooks follow this pattern (example from gsd-prompt-guard.js):
let input = '';
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
clearTimeout(stdinTimeout);
try {
const data = JSON.parse(input);
// Process data
} catch {
// Silent fail — never block tool execution
process.exit(0);
}
});
Defensive Checks:
- Field existence:
const toolName = data.tool_name;thenif (toolName !== 'Write' && toolName !== 'Edit') - Optional chaining:
data.tool_input?.file_path || '' - Null checks:
if (!sessionId) { process.exit(0); } - Default values:
data.cwd || process.cwd(),data.model?.display_name || 'Claude'
File I/O Testing
Patterns for Reliability:
- Synchronous I/O ensures order:
fs.readFileSync()→ process →fs.writeFileSync() - Directory existence checked before writing:
if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); } - Try-catch wraps all file operations that could fail:
try { const bridgeData = JSON.stringify({ ... }); fs.writeFileSync(bridgePath, bridgeData); } catch (e) { // Silent fail -- bridge is best-effort, don't break statusline }
State Machine / Behavior Testing
Context Monitor Debounce Logic (gsd-context-monitor.js):
- Tracks warning state in file:
/tmp/claude-ctx-{session_id}-warned.json - Debounce counter incremented:
warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1 - Severity escalation bypasses debounce:
if (severityEscalated) { // emit immediately } - State reset on warn:
warnData.callsSinceWarn = 0
This pattern validates behavior without formal tests:
let warnData = { callsSinceWarn: 0, lastLevel: null };
const isCritical = remaining <= CRITICAL_THRESHOLD;
const currentLevel = isCritical ? 'critical' : 'warning';
const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
process.exit(0); // Suppress warning
}
Regex Pattern Testing
Prompt Injection Detection (gsd-prompt-guard.js):
Patterns tested against content without formal unit tests:
const INJECTION_PATTERNS = [
/ignore\s+(all\s+)?previous\s+instructions/i,
/override\s+(system|previous)\s+(prompt|instructions)/i,
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
/(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
/\[SYSTEM\]/i,
/<<\s*SYS\s*>>/i,
];
for (const pattern of INJECTION_PATTERNS) {
if (pattern.test(content)) {
findings.push(pattern.source);
}
}
Unicode detection without regex: if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD]/.test(content))
Environment & Configuration Testing
Configuration Loading Safety (all hooks):
- Try-catch with silent fail:
const configPath = path.join(cwd, '.planning', 'config.json'); if (fs.existsSync(configPath)) { try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); if (config.hooks?.context_warnings === false) { process.exit(0); // Feature disabled } } catch (e) { // Ignore config parse errors } } - Optional chaining for nested config:
config.hooks?.workflow_guard,config.hooks?.context_warnings
Environment Variable Access:
const envDir = process.env.CLAUDE_CONFIG_DIR;
if (envDir && fs.existsSync(path.join(envDir, 'get-shit-done', 'VERSION'))) {
return envDir; // Custom config dir detected
}
Performance & Resource Management
Timeout Guards (prevent resource leaks):
- All hooks implement stdin timeout:
const stdinTimeout = setTimeout(() => process.exit(0), 3000); - Longer timeout for high-volume operations:
const stdinTimeout = setTimeout(() => process.exit(0), 10000);in context-monitor - Always cleared before processing:
clearTimeout(stdinTimeout);
Background Process Management (gsd-check-update.js):
- Child process spawned with
stdio: 'ignore': doesn't inherit parent's stdio - Process detached on Windows:
detached: true(required for proper cleanup) - Parent calls
child.unref(): parent doesn't wait for child to exit
const child = spawn(process.execPath, ['-e', `...inline script...`], {
stdio: 'ignore',
windowsHide: true,
detached: true
});
child.unref();
Test Coverage Gaps
Areas Without Formal Testing:
- Regex Pattern Accuracy - Injection patterns untested against false positives/negatives
- Debounce Counter Edge Cases - Corruption recovery, counter reset logic
- Platform-Specific Behavior - Windows vs Linux path handling, process detachment
- Concurrent Access - Multiple hooks writing to same state files simultaneously
- Large Input Handling - No tests for multi-megabyte JSON on stdin
- Stale File Cleanup - No validation that temp files are properly removed
- Spawn Child Behavior - Background update check success/failure not validated
Recommendation for Testing
Given the architecture (CLI hooks, not library code):
- Integration testing more valuable than unit tests
- Manual testing via real Claude Code sessions is current primary validation
- Would recommend:
- Snapshot tests for JSON output structure
- Mock file system for configuration loading paths
- Integration tests simulating tool use hook flow
- Platform-specific testing (Windows, macOS, Linux) for path handling
Lack of tests is acceptable for:
- Simple stdin/stdout data transformation scripts
- Hooks deployed once per installation (not on every tool call)
- Silent-fail-safe design (errors don't break workflows)
- Real-world testing via 50+ GitHub issues and fixes
Testing analysis: 2026-03-27