86 lines
2.5 KiB
JavaScript
Executable file
86 lines
2.5 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
/**
|
|
* Stop hook — scans files changed in the last commit on the current branch,
|
|
* runs MantraValidator on those that look like BYAN agent files, and warns
|
|
* when any drops below the 80% mantra compliance threshold.
|
|
*
|
|
* Scope: .md files under _byan/**, .github/agents/**, .claude/skills/**
|
|
* (agent definitions). Non-blocking: never prevents Stop, only warns via
|
|
* additionalContext.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const THRESHOLD = 80;
|
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
|
|
function changedFiles() {
|
|
try {
|
|
const out = execSync('git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only --cached', {
|
|
cwd: projectDir,
|
|
encoding: 'utf8',
|
|
});
|
|
return out
|
|
.split('\n')
|
|
.map((l) => l.trim())
|
|
.filter(Boolean);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function looksLikeAgentFile(rel) {
|
|
if (!rel.endsWith('.md')) return false;
|
|
return (
|
|
rel.includes('_byan/bmb/agents/') ||
|
|
rel.includes('_byan/agents/') ||
|
|
rel.includes('.github/agents/') ||
|
|
rel.includes('.claude/skills/')
|
|
);
|
|
}
|
|
|
|
function runValidator(absPath) {
|
|
try {
|
|
const MantraValidator = require(path.join(projectDir, 'src/byan-v2/generation/mantra-validator.js'));
|
|
const content = fs.readFileSync(absPath, 'utf8');
|
|
const validator = new MantraValidator();
|
|
const res = validator.validate(content);
|
|
const score = Math.round((res.compliant.length / res.totalMantras) * 100);
|
|
return { score, errors: res.errors || [], warnings: res.warnings || [] };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
}
|
|
|
|
const offenders = [];
|
|
const files = changedFiles().filter(looksLikeAgentFile);
|
|
|
|
for (const rel of files) {
|
|
const abs = path.join(projectDir, rel);
|
|
if (!fs.existsSync(abs)) continue;
|
|
const r = runValidator(abs);
|
|
if (r.error) continue;
|
|
if (r.score < THRESHOLD) {
|
|
offenders.push({ file: rel, score: r.score, errors: r.errors.slice(0, 2) });
|
|
}
|
|
}
|
|
|
|
let additionalContext = '';
|
|
if (offenders.length > 0) {
|
|
const lines = [
|
|
`BYAN mantra validator warning: ${offenders.length} changed agent file(s) below ${THRESHOLD}% threshold.`,
|
|
];
|
|
for (const o of offenders) {
|
|
lines.push(` - ${o.file}: score ${o.score}%`);
|
|
for (const e of o.errors) lines.push(` ${e}`);
|
|
}
|
|
additionalContext = lines.join('\n');
|
|
}
|
|
|
|
if (additionalContext) {
|
|
process.stdout.write(JSON.stringify({ systemMessage: additionalContext, continue: true }));
|
|
} else {
|
|
process.stdout.write(JSON.stringify({ continue: true }));
|
|
}
|