Files
everything-claude-code/scripts/hooks/evaluate-session.js
Affaan Mustafa 492c99ac24 fix: 3 bugs fixed, stdin encoding hardened, 37 CI validator tests added
Bug fixes:
- utils.js: glob-to-regex conversion now escapes all regex special chars
  (+, ^, $, |, (), {}, [], \) before converting * and ? wildcards
- validate-hooks.js: escape sequence processing order corrected —
  \\\\ now processed before \\n and \\t to prevent double-processing
- 6 hooks: added process.stdin.setEncoding('utf8') to prevent
  multi-byte UTF-8 character corruption at chunk boundaries
  (check-console-log, post-edit-format, post-edit-typecheck,
  post-edit-console-warn, session-end, evaluate-session)

New tests (37):
- CI validator test suite (tests/ci/validators.test.js):
  - validate-agents: 9 tests (real project, frontmatter parsing,
    BOM/CRLF, colons in values, missing fields, non-md skip)
  - validate-hooks: 13 tests (real project, invalid JSON, invalid
    event types, missing fields, async/timeout validation, inline JS
    syntax, array commands, legacy format)
  - validate-skills: 6 tests (real project, missing SKILL.md, empty
    files, non-directory entries)
  - validate-commands: 5 tests (real project, empty files, non-md skip)
  - validate-rules: 4 tests (real project, empty files)

Total test count: 228 (up from 191)
2026-02-12 16:08:49 -08:00

100 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Continuous Learning - Session Evaluator
*
* Cross-platform (Windows, macOS, Linux)
*
* Runs on Stop hook to extract reusable patterns from Claude Code sessions.
* Reads transcript_path from stdin JSON (Claude Code hook input).
*
* Why Stop hook instead of UserPromptSubmit:
* - Stop runs once at session end (lightweight)
* - UserPromptSubmit runs every message (heavy, adds latency)
*/
const path = require('path');
const fs = require('fs');
const {
getLearnedSkillsDir,
ensureDir,
readFile,
countInFile,
log
} = require('../lib/utils');
// Read hook input from stdin (Claude Code provides transcript_path via stdin JSON)
const MAX_STDIN = 1024 * 1024;
let stdinData = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (stdinData.length < MAX_STDIN) {
stdinData += chunk;
}
});
process.stdin.on('end', () => {
main().catch(err => {
console.error('[ContinuousLearning] Error:', err.message);
process.exit(0);
});
});
async function main() {
// Parse stdin JSON to get transcript_path
let transcriptPath = null;
try {
const input = JSON.parse(stdinData);
transcriptPath = input.transcript_path;
} catch {
// Fallback: try env var for backwards compatibility
transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
}
// Get script directory to find config
const scriptDir = __dirname;
const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
// Default configuration
let minSessionLength = 10;
let learnedSkillsPath = getLearnedSkillsDir();
// Load config if exists
const configContent = readFile(configFile);
if (configContent) {
try {
const config = JSON.parse(configContent);
minSessionLength = config.min_session_length || 10;
if (config.learned_skills_path) {
// Handle ~ in path
learnedSkillsPath = config.learned_skills_path.replace(/^~/, require('os').homedir());
}
} catch (err) {
log(`[ContinuousLearning] Failed to parse config: ${err.message}, using defaults`);
}
}
// Ensure learned skills directory exists
ensureDir(learnedSkillsPath);
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
process.exit(0);
}
// Count user messages in session (allow optional whitespace around colon)
const messageCount = countInFile(transcriptPath, /"type"\s*:\s*"user"/g);
// Skip short sessions
if (messageCount < minSessionLength) {
log(`[ContinuousLearning] Session too short (${messageCount} messages), skipping`);
process.exit(0);
}
// Signal to Claude that session should be evaluated for extractable patterns
log(`[ContinuousLearning] Session has ${messageCount} messages - evaluate for extractable patterns`);
log(`[ContinuousLearning] Save learned skills to: ${learnedSkillsPath}`);
process.exit(0);
}