mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: migrate hooks to stdin JSON input, fix duplicate main() calls, add threshold validation
- Migrate session-end.js and evaluate-session.js from CLAUDE_TRANSCRIPT_PATH env var to stdin JSON transcript_path (correct hook input mechanism) - Remove duplicate main() calls that ran before stdin was read, causing session files to be created with empty data - Add range validation (1-10000) on COMPACT_THRESHOLD in suggest-compact.js to prevent negative or absurdly large thresholds - Add integration/hooks.test.js to tests/run-all.js so CI runs all 97 tests - Update evaluate-session.sh to parse transcript_path from stdin JSON - Update hooks.test.js to pass transcript_path via stdin instead of env var - Sync .cursor/ copies
This commit is contained in:
@@ -43,8 +43,13 @@ fi
|
|||||||
# Ensure learned skills directory exists
|
# Ensure learned skills directory exists
|
||||||
mkdir -p "$LEARNED_SKILLS_PATH"
|
mkdir -p "$LEARNED_SKILLS_PATH"
|
||||||
|
|
||||||
# Get transcript path from environment (set by Claude Code)
|
# Get transcript path from stdin JSON (Claude Code hook input)
|
||||||
transcript_path="${CLAUDE_TRANSCRIPT_PATH:-}"
|
# Falls back to env var for backwards compatibility
|
||||||
|
stdin_data=$(cat)
|
||||||
|
transcript_path=$(echo "$stdin_data" | grep -o '"transcript_path":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$transcript_path" ]; then
|
||||||
|
transcript_path="${CLAUDE_TRANSCRIPT_PATH:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$transcript_path" ] || [ ! -f "$transcript_path" ]; then
|
if [ -z "$transcript_path" ] || [ ! -f "$transcript_path" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ async function main() {
|
|||||||
const sessionId = process.env.CLAUDE_SESSION_ID || String(process.ppid) || 'default';
|
const sessionId = process.env.CLAUDE_SESSION_ID || String(process.ppid) || 'default';
|
||||||
const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
|
const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
|
||||||
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
||||||
const threshold = Number.isFinite(rawThreshold) ? rawThreshold : 50;
|
const threshold = Number.isFinite(rawThreshold) && rawThreshold > 0 && rawThreshold <= 10000
|
||||||
|
? rawThreshold
|
||||||
|
: 50;
|
||||||
|
|
||||||
let count = 1;
|
let count = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
*
|
*
|
||||||
* Cross-platform (Windows, macOS, Linux)
|
* Cross-platform (Windows, macOS, Linux)
|
||||||
*
|
*
|
||||||
* Runs on Stop hook to extract reusable patterns from Claude Code sessions
|
* 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:
|
* Why Stop hook instead of UserPromptSubmit:
|
||||||
* - Stop runs once at session end (lightweight)
|
* - Stop runs once at session end (lightweight)
|
||||||
@@ -21,7 +22,34 @@ const {
|
|||||||
log
|
log
|
||||||
} = require('../lib/utils');
|
} = 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.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() {
|
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
|
// Get script directory to find config
|
||||||
const scriptDir = __dirname;
|
const scriptDir = __dirname;
|
||||||
const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
|
const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
|
||||||
@@ -49,9 +77,6 @@ async function main() {
|
|||||||
// Ensure learned skills directory exists
|
// Ensure learned skills directory exists
|
||||||
ensureDir(learnedSkillsPath);
|
ensureDir(learnedSkillsPath);
|
||||||
|
|
||||||
// Get transcript path from environment (set by Claude Code)
|
|
||||||
const transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
||||||
|
|
||||||
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
@@ -71,8 +96,3 @@ async function main() {
|
|||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(err => {
|
|
||||||
console.error('[ContinuousLearning] Error:', err.message);
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Cross-platform (Windows, macOS, Linux)
|
* Cross-platform (Windows, macOS, Linux)
|
||||||
*
|
*
|
||||||
* Runs when Claude session ends. Extracts a meaningful summary from
|
* Runs when Claude session ends. Extracts a meaningful summary from
|
||||||
* the session transcript (via CLAUDE_TRANSCRIPT_PATH) and saves it
|
* the session transcript (via stdin JSON transcript_path) and saves it
|
||||||
* to a session file for cross-session continuity.
|
* to a session file for cross-session continuity.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -85,7 +85,38 @@ function extractSessionSummary(transcriptPath) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read hook input from stdin (Claude Code provides transcript_path via stdin JSON)
|
||||||
|
const MAX_STDIN = 1024 * 1024;
|
||||||
|
let stdinData = '';
|
||||||
|
|
||||||
|
process.stdin.on('data', chunk => {
|
||||||
|
if (stdinData.length < MAX_STDIN) {
|
||||||
|
stdinData += chunk;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdin.on('end', () => {
|
||||||
|
runMain();
|
||||||
|
});
|
||||||
|
|
||||||
|
function runMain() {
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('[SessionEnd] Error:', err.message);
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
const sessionsDir = getSessionsDir();
|
const sessionsDir = getSessionsDir();
|
||||||
const today = getDateString();
|
const today = getDateString();
|
||||||
const shortId = getSessionIdShort();
|
const shortId = getSessionIdShort();
|
||||||
@@ -96,7 +127,6 @@ async function main() {
|
|||||||
const currentTime = getTimeString();
|
const currentTime = getTimeString();
|
||||||
|
|
||||||
// Try to extract summary from transcript
|
// Try to extract summary from transcript
|
||||||
const transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
||||||
let summary = null;
|
let summary = null;
|
||||||
|
|
||||||
if (transcriptPath) {
|
if (transcriptPath) {
|
||||||
@@ -183,7 +213,3 @@ function buildSummarySection(summary) {
|
|||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(err => {
|
|
||||||
console.error('[SessionEnd] Error:', err.message);
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ async function main() {
|
|||||||
const sessionId = process.env.CLAUDE_SESSION_ID || String(process.ppid) || 'default';
|
const sessionId = process.env.CLAUDE_SESSION_ID || String(process.ppid) || 'default';
|
||||||
const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
|
const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
|
||||||
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
||||||
const threshold = Number.isFinite(rawThreshold) ? rawThreshold : 50;
|
const threshold = Number.isFinite(rawThreshold) && rawThreshold > 0 && rawThreshold <= 10000
|
||||||
|
? rawThreshold
|
||||||
|
: 50;
|
||||||
|
|
||||||
let count = 1;
|
let count = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,13 @@ fi
|
|||||||
# Ensure learned skills directory exists
|
# Ensure learned skills directory exists
|
||||||
mkdir -p "$LEARNED_SKILLS_PATH"
|
mkdir -p "$LEARNED_SKILLS_PATH"
|
||||||
|
|
||||||
# Get transcript path from environment (set by Claude Code)
|
# Get transcript path from stdin JSON (Claude Code hook input)
|
||||||
transcript_path="${CLAUDE_TRANSCRIPT_PATH:-}"
|
# Falls back to env var for backwards compatibility
|
||||||
|
stdin_data=$(cat)
|
||||||
|
transcript_path=$(echo "$stdin_data" | grep -o '"transcript_path":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$transcript_path" ]; then
|
||||||
|
transcript_path="${CLAUDE_TRANSCRIPT_PATH:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$transcript_path" ] || [ ! -f "$transcript_path" ]; then
|
if [ -z "$transcript_path" ] || [ ! -f "$transcript_path" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -232,9 +232,8 @@ async function runTests() {
|
|||||||
const transcript = Array(5).fill('{"type":"user","content":"test"}\n').join('');
|
const transcript = Array(5).fill('{"type":"user","content":"test"}\n').join('');
|
||||||
fs.writeFileSync(transcriptPath, transcript);
|
fs.writeFileSync(transcriptPath, transcript);
|
||||||
|
|
||||||
const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), '', {
|
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||||
CLAUDE_TRANSCRIPT_PATH: transcriptPath
|
const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), stdinJson);
|
||||||
});
|
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
result.stderr.includes('Session too short'),
|
result.stderr.includes('Session too short'),
|
||||||
@@ -252,9 +251,8 @@ async function runTests() {
|
|||||||
const transcript = Array(15).fill('{"type":"user","content":"test"}\n').join('');
|
const transcript = Array(15).fill('{"type":"user","content":"test"}\n').join('');
|
||||||
fs.writeFileSync(transcriptPath, transcript);
|
fs.writeFileSync(transcriptPath, transcript);
|
||||||
|
|
||||||
const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), '', {
|
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||||
CLAUDE_TRANSCRIPT_PATH: transcriptPath
|
const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), stdinJson);
|
||||||
});
|
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
result.stderr.includes('15 messages'),
|
result.stderr.includes('15 messages'),
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ const testsDir = __dirname;
|
|||||||
const testFiles = [
|
const testFiles = [
|
||||||
'lib/utils.test.js',
|
'lib/utils.test.js',
|
||||||
'lib/package-manager.test.js',
|
'lib/package-manager.test.js',
|
||||||
'hooks/hooks.test.js'
|
'hooks/hooks.test.js',
|
||||||
|
'integration/hooks.test.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log('╔══════════════════════════════════════════════════════════╗');
|
console.log('╔══════════════════════════════════════════════════════════╗');
|
||||||
|
|||||||
Reference in New Issue
Block a user