Files
AgenticCode/.planning/codebase/TESTING.md
2026-03-27 00:21:00 +00:00

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 files
  • gsd-statusline.js (119 lines) - Renders context usage and task status
  • gsd-context-monitor.js (156 lines) - Warns when context window is low
  • gsd-workflow-guard.js (94 lines) - Advises on workflow compliance
  • gsd-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: true for 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; then if (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:

  1. Regex Pattern Accuracy - Injection patterns untested against false positives/negatives
  2. Debounce Counter Edge Cases - Corruption recovery, counter reset logic
  3. Platform-Specific Behavior - Windows vs Linux path handling, process detachment
  4. Concurrent Access - Multiple hooks writing to same state files simultaneously
  5. Large Input Handling - No tests for multi-megabyte JSON on stdin
  6. Stale File Cleanup - No validation that temp files are properly removed
  7. 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:
    1. Snapshot tests for JSON output structure
    2. Mock file system for configuration loading paths
    3. Integration tests simulating tool use hook flow
    4. 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