Assessor_2.0/.claude/hooks/core-release.mjs

89 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// UserPromptSubmit hook: records an explicit "Core-Freigabe: <area>[, <area>]" from the user's message.
// The release covers only this message; every new message replaces or removes it.
// Only the user's own prompt is read, never tool output or file content.
import fs from 'node:fs';
import path from 'node:path';
import { RELEASE_FILE, ALL_AREAS, resolveAreaName } from './core-areas.mjs';
const RELEASE_PHRASE = /core[- ]?freigabe\s*:?\s*/gi;
const SEPARATOR = /^(\s*(,|;|\/|&|\+|\bund\b|\band\b)\s*|\s+)/i;
// Reads area names from the start of the text after the phrase; trailing prose ends the list.
// Any character other than a letter or space (dash, colon, digit, punctuation, newline) ends a name,
// so "Core-Freigabe: Shared Kernel – füge ..." releases Shared Kernel.
function readAreas(tail, unknown) {
const areas = [];
let rest = tail;
while (rest.length > 0) {
const word = rest.match(/^[\p{L}][\p{L} ]*?(?=$|[^\p{L} ]|\s+(und|and)\b)/u)?.[0] ?? '';
const candidates = word.split(' ').map((_, index, parts) => parts.slice(0, parts.length - index).join(' '));
const match = candidates.find((candidate) => resolveAreaName(candidate));
if (!match) {
if (areas.length === 0 && word) unknown.push(word.trim());
break;
}
areas.push(resolveAreaName(match));
rest = rest.slice(match.length);
const separator = rest.match(SEPARATOR);
if (!separator) break;
rest = rest.slice(separator[0].length);
}
return areas;
}
function parseRelease(prompt) {
const areas = new Set();
const unknown = [];
for (const match of prompt.matchAll(RELEASE_PHRASE)) {
const tail = prompt.slice(match.index + match[0].length);
for (const area of readAreas(tail, unknown)) areas.add(area);
}
return { areas: [...areas], unknown };
}
function label(area) {
return area === ALL_AREAS ? 'alle Core-Bereiche ausser Governance' : area;
}
function main(raw) {
const input = JSON.parse(raw);
const projectDir = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();
const releaseFile = path.join(projectDir, RELEASE_FILE);
const { areas, unknown } = parseRelease(String(input.prompt ?? ''));
fs.rmSync(releaseFile, { force: true });
const output = {};
if (areas.length > 0) {
fs.writeFileSync(releaseFile, JSON.stringify({ sessionId: input.session_id ?? null, areas, createdAt: new Date().toISOString() }));
const text = areas.map(label).join(', ');
output.systemMessage = `✅ CORE-FREIGABE für diese Nachricht: ${text}`;
output.hookSpecificOutput = {
hookEventName: 'UserPromptSubmit',
additionalContext: `The user released these Core areas for this message only: ${text}. Other Core areas stay blocked.`,
};
}
if (unknown.length > 0) {
output.systemMessage = [output.systemMessage, `⚠ Core-Freigabe nicht erkannt: "${unknown.join('", "')}". Gueltige Bereiche: Ingest, Normalization, Evaluation, Store, Disclosure, Reference Catalogs, Shared Kernel, Presentation, Platform, Core Tests, Governance, alle.`]
.filter(Boolean).join('\n');
}
if (Object.keys(output).length > 0) process.stdout.write(JSON.stringify(output));
}
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => { raw += chunk; });
process.stdin.on('end', () => {
try {
main(raw);
} catch (error) {
// Fail closed: without a readable message no release remains active.
try {
fs.rmSync(path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), RELEASE_FILE), { force: true });
} catch {}
process.stdout.write(JSON.stringify({
systemMessage: `⚠ Core-Freigabe konnte nicht gelesen werden (${error instanceof Error ? error.message : 'unbekannter Fehler'}). Der Core bleibt gesperrt.`,
}));
}
});