86 lines
2.9 KiB
JavaScript
86 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* UserPromptSubmit hook — BYAN Strict Mode context injector.
|
|
*
|
|
* Two behaviors :
|
|
* - When a strict session is engaged, inject the strict banner plus a live
|
|
* status line (passes done / required, locked scope hash) so the agent
|
|
* stays anchored to the contract on every turn.
|
|
* - When no session is engaged but the user's prompt contains an activation
|
|
* keyword (prod, production, client, contrat, ...), inject a suggestion to
|
|
* lock strict mode before building. It suggests, it does not auto-lock.
|
|
*
|
|
* Emits empty context on any error.
|
|
*/
|
|
|
|
const {
|
|
loadConfig,
|
|
loadState,
|
|
isEngaged,
|
|
passCount,
|
|
lastVerdict,
|
|
readStdin,
|
|
parseJson,
|
|
} = require('./lib/strict-runtime');
|
|
|
|
function findKeyword(prompt, keywords) {
|
|
if (!prompt || !Array.isArray(keywords)) return null;
|
|
const lower = prompt.toLowerCase();
|
|
for (const k of keywords) {
|
|
const kw = String(k).toLowerCase();
|
|
if (/^[a-z]+$/.test(kw)) {
|
|
if (new RegExp(`\\b${kw}\\b`).test(lower)) return k;
|
|
} else if (lower.includes(kw)) {
|
|
return k;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Pure : returns the additionalContext string (possibly empty).
|
|
function buildContext({ state, config, prompt }) {
|
|
if (isEngaged(state)) {
|
|
const minPasses = (config && config.min_passes) || 3;
|
|
const banner = (config && config.banners && config.banners.context) || '[STRICT MODE ACTIVE]';
|
|
const done = passCount(state);
|
|
const hash = state.scope_lock ? state.scope_lock.scope_hash : 'unknown';
|
|
return (
|
|
`${banner}\n` +
|
|
`Locked scope: ${hash} | self-verify ${done}/${minPasses} | last verdict ${lastVerdict(state) || 'none'}.\n` +
|
|
`Stay inside the locked scope. Do not declare done before byan_strict_complete returns an audit token.`
|
|
);
|
|
}
|
|
|
|
const keyword = findKeyword(prompt, config && config.auto_keywords);
|
|
if (keyword) {
|
|
return (
|
|
`[STRICT MODE SUGGESTED]\n` +
|
|
`The request mentions "${keyword}", which signals a production-grade deliverable. ` +
|
|
`Before building, consider locking strict mode with byan_strict_lock_scope ` +
|
|
`(verbatim scope + testable acceptance criteria). Strict mode enforces ` +
|
|
`>= ${(config && config.min_passes) || 3} self-verify passes and a 95% confidence floor on hard claims. ` +
|
|
`Confirm with the user, then lock.`
|
|
);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
if (require.main === module) {
|
|
(async () => {
|
|
const state = loadState();
|
|
const config = loadConfig();
|
|
const payload = parseJson(await readStdin());
|
|
const prompt = payload.prompt || payload.user_prompt || payload.userPrompt || '';
|
|
|
|
const additionalContext = buildContext({ state, config, prompt });
|
|
process.stdout.write(
|
|
JSON.stringify({
|
|
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext },
|
|
})
|
|
);
|
|
process.exit(0);
|
|
})();
|
|
}
|
|
|
|
module.exports = { buildContext, findKeyword };
|