2026-01-23 15:08:07 +08:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
/**
|
|
|
|
|
* Continuous Learning - Session Evaluator
|
|
|
|
|
*
|
|
|
|
|
* Cross-platform (Windows, macOS, Linux)
|
|
|
|
|
*
|
2026-02-12 15:33:55 -08:00
|
|
|
* Runs on Stop hook to extract reusable patterns from Claude Code sessions.
|
|
|
|
|
* Reads transcript_path from stdin JSON (Claude Code hook input).
|
2026-01-23 15:08:07 +08:00
|
|
|
*
|
|
|
|
|
* 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');
|
|
|
|
|
|
2026-02-12 15:33:55 -08:00
|
|
|
// Read hook input from stdin (Claude Code provides transcript_path via stdin JSON)
|
|
|
|
|
const MAX_STDIN = 1024 * 1024;
|
|
|
|
|
let stdinData = '';
|
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
|
|
|
process.stdin.setEncoding('utf8');
|
2026-02-12 15:33:55 -08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-23 15:08:07 +08:00
|
|
|
async function main() {
|
2026-02-12 15:33:55 -08:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 15:08:07 +08:00
|
|
|
// 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);
|
fix: nullish coalescing in evaluate-session config, narrow pre-compact glob, add 11 tests
- evaluate-session.js: || 10 → ?? 10 for min_session_length (0 is valid)
- pre-compact.js: *.tmp → *-session.tmp to match only session files
- 11 new tests: config loading (min=0, null, custom path, invalid JSON),
session-end update path (timestamp, template replace, preserve content),
pre-compact glob specificity, extractSessionSummary edge cases
2026-02-13 02:42:01 -08:00
|
|
|
minSessionLength = config.min_session_length ?? 10;
|
2026-01-23 15:08:07 +08:00
|
|
|
|
|
|
|
|
if (config.learned_skills_path) {
|
|
|
|
|
// Handle ~ in path
|
|
|
|
|
learnedSkillsPath = config.learned_skills_path.replace(/^~/, require('os').homedir());
|
|
|
|
|
}
|
fix: add try-catch to inline hooks, fix schema drift
- Wrap JSON.parse in try-catch for all 6 inline hooks in hooks.json
(dev-server blocker, tmux reminder, git-push reminder, doc blocker,
PR create logger, build analysis) — previously unguarded JSON.parse
would crash on empty/malformed stdin, preventing data passthrough
- Add config parse error logging to evaluate-session.js
- Fix plugin.schema.json: author can be string or {name,url} object,
add version (semver pattern), homepage, keywords, skills, agents
- Fix package-manager.schema.json: add setAt (date-time) field and
make packageManager required to match actual code behavior
2026-02-12 14:38:00 -08:00
|
|
|
} catch (err) {
|
|
|
|
|
log(`[ContinuousLearning] Failed to parse config: ${err.message}, using defaults`);
|
2026-01-23 15:08:07 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure learned skills directory exists
|
|
|
|
|
ensureDir(learnedSkillsPath);
|
|
|
|
|
|
|
|
|
|
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
fix: 2 bugs fixed, 17 tests added for hook scripts
Bug fixes:
- evaluate-session.js: whitespace-tolerant regex for counting user
messages in JSONL transcripts (/"type":"user"/ → /"type"\s*:\s*"user"/)
- session-end.js: guard against null elements in content arrays
(c.text → (c && c.text) to prevent TypeError)
New tests (17):
- evaluate-session: whitespace JSON regression test
- session-end: null content array elements regression test
- post-edit-console-warn: 5 tests (warn, skip non-JS, clean files,
missing file, stdout passthrough)
- post-edit-format: 3 tests (empty stdin, non-JS skip, invalid JSON)
- post-edit-typecheck: 4 tests (empty stdin, non-TS skip, missing file,
no tsconfig)
Total test count: 191 (up from 164)
2026-02-12 16:02:31 -08:00
|
|
|
// Count user messages in session (allow optional whitespace around colon)
|
|
|
|
|
const messageCount = countInFile(transcriptPath, /"type"\s*:\s*"user"/g);
|
2026-01-23 15:08:07 +08:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|