site-mariage/.claude/hooks/fd-phase-guard.js
Corentin Joguet bff653acd6 first commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:30:37 +02:00

121 lines
5.8 KiB
JavaScript
Executable file
Raw 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 — FD phase guard.
*
* When an FD cycle is active (fd-state.json exists and phase is not
* COMPLETED/ABORTED), inject a strong reminder into additionalContext
* describing the current phase and its hard rules. Makes it mechanical
* for Claude to stay in the right phase instead of drifting.
*
* Non-blocking : if fd-state is missing or invalid, emit empty context.
*/
const fs = require('fs');
const path = require('path');
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
const statePath = path.join(projectDir, '_byan-output', 'fd-state.json');
const PHASE_RULES = {
DISCOVERY: [
'Identify the project before any ideation. Zero feature on a blurry context.',
'Try local context first (cwd, CLAUDE.md, _byan/config.yaml, README). If unsure, ask the user.',
'Fetch a project summary : MCP first (byan_list_projects, byan_api_projects_get), local fallback only if MCP unavailable or out-of-BYAN.',
'Persist the result via update({ patch: { project_context: { name, slug, domain, stack, summary, source } } }).',
'Prefix every response with [FD:DISCOVERY].',
'Exit only when project_context is set and user confirms "ok c\'est ce projet".',
],
BRAINSTORM: [
'Quantity > quality. No idea rejected. Role-play Carson (brainstorming coach).',
'Use YES AND to extend every user seed. Apply inversion, analogies.',
'DO NOT ask pruning questions. DO NOT apply Ockham yet. Push ideas.',
'Prefix every response with [FD:BRAINSTORM].',
'Exit only when user says "stop brainstorm" / "ok j\'ai mes idees", or raw_ideas >= 10.',
],
PRUNE: [
'Challenge Before Confirm (Mantra IA-16). Apply Ockham\'s Razor (#37). YAGNI.',
'For each idea : ask "probleme resolu ?" "necessaire MAINTENANT ?" "MVP ?"',
'Cluster similar ideas, kill redundant, priority-rank what survives (P1/P2/P3).',
'Prefix every response with [FD:PRUNE].',
'Exit only when user says "OK backlog" or equivalent explicit validation.',
],
DISPATCH: [
'Map each feature to {component × specialist × model × strategy × est_tokens}.',
'Use byan_dispatch MCP to compute strategy if uncertain. Surface missing specialist.',
'Prefix every response with [FD:DISPATCH].',
'Exit only when user validates the dispatch table explicitly.',
],
BUILD: [
'Delegate to byan-hermes-dispatch. One commit per feature. TDD : tests before code.',
'Atomic commits : `type: description`, no emoji, zero cross-feature noise.',
'Prefix every response with [FD:BUILD].',
'Exit only when all backlog items show status=done AND user validates the diffs.',
],
REVIEW: [
'Pre-flight humain before VALIDATE — Quinn (QA) inspects the diff against VALIDATE criteria.',
'Check : readability, naming, side effects, coverage per branch, comments justified, zero emoji.',
'Cross-check planned tests vs implemented tests. Cross-check mantra risks vs actual code.',
'Output : { status: "ready-for-validate" | "needs-rework", findings: [...] }. Persist via update({ patch: { review_findings: [...] } }).',
'Prefix every response with [FD:REVIEW].',
'Exit : ready-for-validate -> advance to VALIDATE. needs-rework -> short-circuit to REFACTOR (skip VALIDATE this cycle).',
],
VALIDATE: [
'Run npm test. Zero regression on previously-passing tests.',
'MantraValidator >= 80% on changed agent/skill files. Fact-check any absolute claim.',
'Decision is binary : OK -> DOC, KO -> REFACTOR. Persist via update({ patch: { validate_verdict: { status, blocking_issues } } }).',
'Prefix every response with [FD:VALIDATE].',
'Exit : tests green + score >= 80% -> advance to DOC. Otherwise -> advance to REFACTOR.',
],
REFACTOR: [
'Corrective loop only — no new features, no re-design. Address blocking_issues from VALIDATE.',
'For each issue : reproduce, minimal fix (Ockham), re-run check. Commits : `fix: [issue]`.',
'Persist progress via update({ patch: { refactor_log: [...] } }).',
'Prefix every response with [FD:REFACTOR].',
'Exit : all blocking_issues resolved -> loop back to BUILD (then REVIEW -> VALIDATE again).',
'Guard-rail : 3 consecutive BUILD->REVIEW->VALIDATE->REFACTOR cycles without convergence -> propose retour to PRUNE or ABORTED.',
],
DOC: [
'Documentation is a deliverable. Role-play Paige (tech-writer) or delegate to bmad-bmm-tech-writer.',
'Update CHANGELOG.md (dated entry, type: description). Update README.md if public surface changed.',
'Update usage guide (command, example, edge cases). Sync agent-manifest.csv / workflow-manifest.csv if applicable.',
'Bump version (semver) if needed : minor for feature, major for breaking. No emoji, clarity first.',
'Persist what was written via update({ patch: { doc_log: [...] } }).',
'Prefix every response with [FD:DOC].',
'Exit only when user reviews the doc and says "ok doc". Then advance to COMPLETED.',
],
};
function readState() {
try {
if (!fs.existsSync(statePath)) return null;
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
} catch {
return null;
}
}
const state = readState();
let additionalContext = '';
if (state && !['COMPLETED', 'ABORTED'].includes(state.phase)) {
const rules = PHASE_RULES[state.phase] || ['(unknown phase — fall back to conservative behavior)'];
const header = `FD active — phase ${state.phase} — feature ${state.feature_name || '?'} (id ${state.fd_id || '?'})`;
const body = rules.map((r) => ` - ${r}`).join('\n');
additionalContext = [
header,
'',
'Hard rules for this turn :',
body,
'',
`Use byan_fd_status MCP to read full state if needed. Do not hand-edit fd-state.json.`,
].join('\n');
}
process.stdout.write(
JSON.stringify({
hookSpecificOutput: {
hookEventName: 'UserPromptSubmit',
additionalContext: additionalContext,
},
})
);