/**
* Role Enforcement Module - Verifier and Critic role enforcement
*
* 1. Role definitions (mustDo, mustNotDo)
* 2. Output validation (role compliance)
* 3. Compliance tracking
* 4. Role alternation enforcement
*/
import {
VerifierRole,
RoleContext,
RoleComplianceResult,
RoleViolation,
RoleWarning,
RoleEnforcementConfig,
RolePrompt,
PreviousRoundSummary,
ExistingIssueSummary,
// Intent-based types
IntentBasedRoleDefinition,
SuccessCriterion,
RoleConstraint,
FocusIntent,
isIntentBasedRole
} from './types.js';
import {
ROLE_DEFINITIONS,
ROLE_PROMPTS,
VERIFIER_ROLE,
CRITIC_ROLE
} from './definitions.js';
import { Session } from '../types/index.js';
import { ROLE_CONSTANTS } from '../config/constants.js';
import {
shouldUseConciseMode,
getConciseVerifierPrompt,
getConciseCriticPrompt,
CONCISE_VALIDATION_CRITERIA,
ConciseModeConfig,
DEFAULT_CONCISE_CONFIG
} from './concise-prompts.js';
// [ENH: TOKEN-OPT] Compressed prompts - re-export at bottom
export {
getCompressedPrompt,
COMPRESSED_VERIFIER_PROMPT,
COMPRESSED_CRITIC_PROMPT,
DEFAULT_COMPRESSED_CONFIG,
estimateCompressedSavings,
type CompressedPromptConfig
} from './compressed-prompts.js';
// [ENH: I18N] Multi-language prompt templates
import {
type SupportedLanguage as I18nSupportedLanguage,
getVerifierPrompt as getI18nVerifierPrompt,
getCriticPrompt as getI18nCriticPrompt
} from './i18n-prompts.js';
export type SupportedLanguage = I18nSupportedLanguage;
export {
VERIFIER_PROMPTS,
CRITIC_PROMPTS,
detectLanguage,
getVerifierPrompt as getI18nVerifier,
getCriticPrompt as getI18nCritic,
getRolePrompts,
LANGUAGE_METADATA,
SUPPORTED_LANGUAGES
} from './i18n-prompts.js';
// [ENH: TOKEN-OPT] Structured output - re-export at bottom
export {
VerifierOutputSchema,
CriticOutputSchema,
getStructuredOutputInstruction,
validateVerifierOutput,
validateCriticOutput,
estimateStructuredTokenSavings
} from './structured-output.js';
// [ENH: DYNAMIC-ROLES] Dynamic role lookup
import { getDynamicRolePrompt as getDynamicRolePromptFromStore } from './dynamic-roles-store.js';
// =============================================================================
// State Management
// =============================================================================
/**
* [FIX: MNT-02] Role alternation configuration
*/
interface RoleAlternation {
expectedRole: VerifierRole;
nextRole: VerifierRole;
history: Array<{ role: VerifierRole; round: number }>;
}
interface RoleState {
sessionId: string;
complianceHistory: RoleComplianceResult[];
currentExpectedRole: VerifierRole;
config: RoleEnforcementConfig;
// [FIX: MNT-02] Consolidated role alternation tracking
alternation: RoleAlternation;
}
const roleStates = new Map<string, RoleState>();
/**
* [FIX: MNT-01] Compliance scoring constants - uses centralized constants
*/
const COMPLIANCE_SCORE = ROLE_CONSTANTS.COMPLIANCE_SCORE;
const DEFAULT_CONFIG: RoleEnforcementConfig = {
strictMode: false,
minComplianceScore: ROLE_CONSTANTS.DEFAULT_MIN_COMPLIANCE_SCORE,
allowRoleSwitch: false,
requireAlternation: true
};
// =============================================================================
// Initialization
// =============================================================================
/**
* Initialize role enforcement for session
*/
export function initializeRoleEnforcement(
sessionId: string,
config?: Partial<RoleEnforcementConfig>
): RoleState {
// [FIX: MNT-02] Initialize with consolidated alternation structure
const state: RoleState = {
sessionId,
complianceHistory: [],
currentExpectedRole: 'verifier', // Verifier always goes first
config: { ...DEFAULT_CONFIG, ...config },
alternation: {
expectedRole: 'verifier',
nextRole: 'critic',
history: []
}
};
roleStates.set(sessionId, state);
return state;
}
/**
* Get role state
*/
export function getRoleState(sessionId: string): RoleState | undefined {
return roleStates.get(sessionId);
}
// =============================================================================
// Role Enforcement
// =============================================================================
/**
* Validate role compliance
*/
export function validateRoleCompliance(
sessionId: string,
role: VerifierRole,
output: string,
session: Session
): RoleComplianceResult {
const state = roleStates.get(sessionId);
if (!state) {
// Auto-initialize
initializeRoleEnforcement(sessionId);
return validateRoleCompliance(sessionId, role, output, session);
}
const violations: RoleViolation[] = [];
const warnings: RoleWarning[] = [];
const suggestions: string[] = [];
// 1. Role alternation validation
if (state.config.requireAlternation) {
const alternationResult = checkRoleAlternation(state, role);
if (!alternationResult.valid) {
violations.push({
criterionId: 'ALT001',
severity: 'ERROR',
message: alternationResult.message,
fix: `Expected role: ${state.currentExpectedRole}`
});
}
}
// 2. Apply role-specific validation criteria
const roleDefinition = ROLE_DEFINITIONS[role];
const context = buildRoleContext(sessionId, session);
for (const criterion of roleDefinition.validationCriteria) {
const result = criterion.check(output, context);
if (!result.passed) {
if (criterion.severity === 'ERROR') {
violations.push({
criterionId: criterion.id,
severity: 'ERROR',
message: result.message,
evidence: result.details?.join('\n')
});
} else {
warnings.push({
type: criterion.id,
message: result.message,
suggestion: result.details?.[0] || 'Review required'
});
}
}
}
// 3. Validate role-specific required elements
const requiredCheck = checkRequiredElements(role, output);
violations.push(...requiredCheck.violations);
warnings.push(...requiredCheck.warnings);
// 4. Calculate score
const score = calculateComplianceScore(violations, warnings, roleDefinition.validationCriteria.length);
// 5. Generate suggestions
suggestions.push(...generateSuggestions(role, violations, warnings));
const result: RoleComplianceResult = {
role,
round: session.currentRound + 1,
isCompliant: violations.filter(v => v.severity === 'ERROR').length === 0 &&
score >= state.config.minComplianceScore,
score,
violations,
warnings,
suggestions
};
// [FIX: MNT-02] Update state - use consolidated alternation structure
state.complianceHistory.push(result);
const nextRole = role === 'verifier' ? 'critic' : 'verifier';
state.currentExpectedRole = nextRole;
state.alternation = {
expectedRole: nextRole,
nextRole: role, // [FIX: COR-01] Current role stored as "next after expectedRole" (i.e., the role that comes after nextRole)
history: [...state.alternation.history, { role, round: session.currentRound + 1 }]
};
return result;
}
/**
* Validate role alternation
* [FIX: MNT-02] Use alternation structure for validation
*/
function checkRoleAlternation(
state: RoleState,
attemptedRole: VerifierRole
): { valid: boolean; message: string } {
const expectedRole = state.alternation.expectedRole;
if (attemptedRole !== expectedRole) {
return {
valid: false,
message: `Role alternation violation: Expected ${expectedRole}, but ${attemptedRole} was submitted`
};
}
return { valid: true, message: '' };
}
/**
* Build role context
*/
function buildRoleContext(sessionId: string, session: Session): RoleContext {
const previousRounds: PreviousRoundSummary[] = session.rounds.map(r => ({
round: r.number,
role: r.role as VerifierRole,
issuesRaised: r.issuesRaised,
issuesChallenged: [], // TODO: Add tracking
issuesResolved: r.issuesResolved
}));
const existingIssues: ExistingIssueSummary[] = session.issues.map(i => ({
id: i.id,
severity: i.severity,
status: i.status,
raisedBy: i.raisedBy as VerifierRole,
challengedBy: undefined // TODO: Add tracking
}));
return {
sessionId,
currentRound: session.currentRound,
previousRounds,
existingIssues,
targetFiles: Array.from(session.context.files.keys())
};
}
/**
* Validate required elements
*/
function checkRequiredElements(
role: VerifierRole,
output: string
): { violations: RoleViolation[]; warnings: RoleWarning[] } {
const violations: RoleViolation[] = [];
const warnings: RoleWarning[] = [];
if (role === 'verifier') {
// Verifier required elements
if (!output.match(/(SEC|COR|REL|MNT|PRF)-\d+/) && !output.includes('이슈 없음') && !output.includes('no issues')) {
warnings.push({
type: 'MISSING_ISSUE_FORMAT',
message: 'Standard issue ID format (SEC-01, etc.) not found',
suggestion: 'If there are issues, specify them in SEC-XX, COR-XX format'
});
}
if (!output.match(/\w+\.\w+:\d+/) && output.match(/(SEC|COR|REL|MNT|PRF)-\d+/)) {
violations.push({
criterionId: 'REQ001',
severity: 'WARNING',
message: 'Issue location (file:line) not specified',
fix: 'Specify location in file:line format for each issue'
});
}
}
if (role === 'critic') {
// Critic required elements
if (!output.match(/\b(VALID|INVALID|PARTIAL)\b/gi)) {
warnings.push({
type: 'MISSING_VERDICT',
message: 'Issue verdict (VALID/INVALID/PARTIAL) not found',
suggestion: 'Specify VALID, INVALID, or PARTIAL for each issue'
});
}
if (!output.match(/근거|이유|reasoning|because/gi) && output.match(/INVALID/gi)) {
violations.push({
criterionId: 'REQ002',
severity: 'WARNING',
message: 'INVALID verdict lacks reasoning',
fix: 'Provide specific reasoning when refuting'
});
}
}
return { violations, warnings };
}
/**
* Calculate compliance score
* [FIX: MNT-01] Use COMPLIANCE_SCORE constants
*/
function calculateComplianceScore(
violations: RoleViolation[],
warnings: RoleWarning[],
_totalCriteria: number
): number {
const errorCount = violations.filter(v => v.severity === 'ERROR').length;
const warningCount = violations.filter(v => v.severity === 'WARNING').length + warnings.length;
// [FIX: MNT-01] Use constants instead of magic numbers
const score = COMPLIANCE_SCORE.BASE
- (errorCount * COMPLIANCE_SCORE.ERROR_PENALTY)
- (warningCount * COMPLIANCE_SCORE.WARNING_PENALTY);
return Math.max(COMPLIANCE_SCORE.MIN_SCORE, Math.min(COMPLIANCE_SCORE.MAX_SCORE, score));
}
/**
* Generate improvement suggestions
*/
function generateSuggestions(
role: VerifierRole,
violations: RoleViolation[],
_warnings: RoleWarning[]
): string[] {
const suggestions: string[] = [];
const prompt = ROLE_PROMPTS[role];
if (violations.length > 0) {
suggestions.push(`Check the ${role === 'verifier' ? 'Verifier' : 'Critic'} role checklist:`);
suggestions.push(...prompt.checklist.slice(0, 3));
}
// Role-specific suggestions
if (role === 'verifier') {
if (violations.some(v => v.criterionId === 'V001')) {
suggestions.push('💡 Include evidence in code blocks for all issues');
}
if (violations.some(v => v.criterionId === 'V003')) {
suggestions.push('💡 Do not re-raise issues refuted in previous rounds without new evidence');
}
}
if (role === 'critic') {
if (violations.some(v => v.criterionId === 'C001')) {
suggestions.push('💡 Must provide verdict for all issues raised by Verifier');
}
if (violations.some(v => v.criterionId === 'C002')) {
suggestions.push('💡 Finding new issues is the Verifier\'s role. Only review existing issues');
}
}
return suggestions;
}
// =============================================================================
// Public API
// =============================================================================
/**
* Get next expected role
*/
export function getExpectedRole(sessionId: string): VerifierRole {
const state = roleStates.get(sessionId);
return state?.currentExpectedRole || 'verifier';
}
/**
* Get role prompt
* [ENH: CONCISE] Supports concise mode for round 2+
* [ENH: I18N] Supports language selection
* [ENH: DYNAMIC-ROLES] Supports dynamic roles generated from requirements
*/
export function getRolePrompt(
role: VerifierRole,
options?: {
round?: number;
useConciseMode?: boolean;
language?: SupportedLanguage;
sessionId?: string; // [ENH: DYNAMIC-ROLES] Session ID for dynamic role lookup
}
): RolePrompt {
// [ENH: DYNAMIC-ROLES] Check for dynamic roles first
if (options?.sessionId) {
const dynamicPrompt = getDynamicRolePromptForSession(options.sessionId, role);
if (dynamicPrompt) {
return dynamicPrompt;
}
}
// [ENH: I18N] If language specified, use i18n prompts
if (options?.language && options.language !== 'en') {
return role === 'verifier'
? getI18nVerifierPrompt(options.language)
: getI18nCriticPrompt(options.language);
}
// If concise mode is enabled and round >= 2, return concise prompt
if (options?.useConciseMode && options?.round && options.round >= 2) {
return role === 'verifier'
? getConciseVerifierPrompt(options.round)
: getConciseCriticPrompt(options.round);
}
return ROLE_PROMPTS[role];
}
/**
* Get dynamic role prompt for a session (if set)
* [ENH: DYNAMIC-ROLES]
*/
function getDynamicRolePromptForSession(
sessionId: string,
role: VerifierRole
): RolePrompt | null {
// [ENH: DYNAMIC-ROLES] Use imported function from dynamic-roles-store
return getDynamicRolePromptFromStore(sessionId, role);
}
/**
* Check if concise mode should be used for next round
* [ENH: CONCISE]
*/
export function shouldUseConciseModeForSession(
session: Session,
nextRound: number
): boolean {
return shouldUseConciseMode(session, nextRound);
}
/**
* Re-export concise mode utilities
*/
export { CONCISE_VALIDATION_CRITERIA, DEFAULT_CONCISE_CONFIG };
export type { ConciseModeConfig };
/**
* Get role definition
*/
export function getRoleDefinition(role: VerifierRole) {
return ROLE_DEFINITIONS[role];
}
/**
* Get compliance history
*/
export function getComplianceHistory(sessionId: string): RoleComplianceResult[] {
return roleStates.get(sessionId)?.complianceHistory || [];
}
/**
* Update role enforcement config
*/
export function updateRoleConfig(
sessionId: string,
config: Partial<RoleEnforcementConfig>
): RoleEnforcementConfig | null {
const state = roleStates.get(sessionId);
if (!state) return null;
state.config = { ...state.config, ...config };
return state.config;
}
/**
* Get role enforcement summary
*/
export function getRoleEnforcementSummary(sessionId: string): object | null {
const state = roleStates.get(sessionId);
if (!state) return null;
const history = state.complianceHistory;
const verifierResults = history.filter(r => r.role === 'verifier');
const criticResults = history.filter(r => r.role === 'critic');
const avgVerifierScore = verifierResults.length > 0
? verifierResults.reduce((sum, r) => sum + r.score, 0) / verifierResults.length
: 0;
const avgCriticScore = criticResults.length > 0
? criticResults.reduce((sum, r) => sum + r.score, 0) / criticResults.length
: 0;
const totalViolations = history.reduce((sum, r) => sum + r.violations.length, 0);
const totalWarnings = history.reduce((sum, r) => sum + r.warnings.length, 0);
return {
sessionId,
config: state.config,
currentExpectedRole: state.currentExpectedRole,
// [FIX: MNT-02] Include alternation info in summary
alternation: {
expectedRole: state.alternation.expectedRole,
nextRole: state.alternation.nextRole,
totalAlternations: state.alternation.history.length
},
stats: {
totalRounds: history.length,
verifierRounds: verifierResults.length,
criticRounds: criticResults.length,
avgVerifierScore: avgVerifierScore.toFixed(1),
avgCriticScore: avgCriticScore.toFixed(1),
totalViolations,
totalWarnings,
complianceRate: history.length > 0
? ((history.filter(r => r.isCompliant).length / history.length) * 100).toFixed(1) + '%'
: 'N/A'
},
recentViolations: history
.flatMap(r => r.violations)
.slice(-5)
.map(v => ({ id: v.criterionId, message: v.message }))
};
}
// =============================================================================
// Cache Cleanup
// =============================================================================
/**
* [FIX: REL-02] Delete role state from memory cache
* Called when session is ended to prevent memory leaks
*/
export function deleteRoleState(sessionId: string): boolean {
return roleStates.delete(sessionId);
}
// =============================================================================
// Export for Tools
// =============================================================================
export {
VERIFIER_ROLE,
CRITIC_ROLE,
ROLE_DEFINITIONS,
ROLE_PROMPTS
};
// =============================================================================
// [ENH: DYNAMIC-ROLES] Dynamic Role Generation
// =============================================================================
export {
generateDynamicRoles,
detectDomain,
generateVerifierRole,
generateCriticRole,
clearRoleCache,
getRoleCacheStats,
DEFAULT_DYNAMIC_ROLE_CONFIG,
type DynamicRoleConfig,
type GeneratedRoles,
type SamplingFunction,
type SamplingRequest,
type SamplingResponse,
} from './dynamic-generator.js';
export {
VERIFIER_ROLE_META_PROMPT,
CRITIC_ROLE_META_PROMPT,
DOMAIN_DETECTION_META_PROMPT,
buildMetaPrompt,
type GeneratedVerifierRole,
type GeneratedCriticRole,
type DomainDetectionResult,
type GeneratedCategory,
} from './meta-prompt.js';
// =============================================================================
// [ENH: INTENT-BASED] Intent-Based Role Helpers
// =============================================================================
/**
* Check if a role definition uses the intent-based contract pattern
*/
export { isIntentBasedRole };
/**
* Export intent-based types for external use
*/
export type {
IntentBasedRoleDefinition,
SuccessCriterion,
RoleConstraint,
FocusIntent
};
/**
* Get focus intents for a role (if intent-based)
* Returns semantic descriptions of what the role should focus on
*/
export function getRoleFocusIntents(role: VerifierRole): FocusIntent[] {
const definition = ROLE_DEFINITIONS[role];
if (isIntentBasedRole(definition)) {
return definition.focusIntents.filter(fi => fi.enabled);
}
return [];
}
/**
* Get success criteria for a role (if intent-based)
* Returns outcome-based success definitions
*/
export function getRoleSuccessCriteria(role: VerifierRole): SuccessCriterion[] {
const definition = ROLE_DEFINITIONS[role];
if (isIntentBasedRole(definition)) {
return definition.successCriteria;
}
return [];
}
/**
* Get constraints for a role (if intent-based)
* Returns guardrails and boundaries
*/
export function getRoleConstraints(role: VerifierRole): RoleConstraint[] {
const definition = ROLE_DEFINITIONS[role];
if (isIntentBasedRole(definition)) {
return definition.constraints.filter(c => c.enabled);
}
return [];
}
/**
* Get self-review prompts for a role (if intent-based)
* Returns outcome-focused questions for self-verification
*/
export function getRoleSelfReviewPrompts(role: VerifierRole): string[] {
const definition = ROLE_DEFINITIONS[role];
if (isIntentBasedRole(definition)) {
return definition.selfReviewPrompts || [];
}
return [];
}
/**
* Generate an intent-based prompt section for a role
* This creates a LLM-friendly representation of the role's intents
*/
export function generateIntentBasedPromptSection(role: VerifierRole): string {
const definition = ROLE_DEFINITIONS[role];
if (!isIntentBasedRole(definition)) {
return '';
}
const sections: string[] = [];
// Success Criteria Section
sections.push('## Success Criteria (What Success Looks Like)');
sections.push('');
for (const sc of definition.successCriteria.filter(s => s.required)) {
sections.push(`- **${sc.description}**`);
sections.push(` _Rationale: ${sc.rationale}_`);
}
sections.push('');
// Focus Intents Section
sections.push('## Focus Areas (What to Think About)');
sections.push('');
for (const fi of definition.focusIntents.filter(f => f.enabled)) {
sections.push(`### ${fi.name} [${fi.priority.toUpperCase()}]`);
sections.push(fi.description);
if (fi.examples && fi.examples.length > 0) {
sections.push('Examples:');
for (const ex of fi.examples) {
sections.push(`- ${ex}`);
}
}
sections.push('');
}
// Constraints Section
sections.push('## Constraints (Guardrails)');
sections.push('');
const mustConstraints = definition.constraints.filter(c => c.enabled && c.type === 'must');
const mustNotConstraints = definition.constraints.filter(c => c.enabled && c.type === 'must-not');
if (mustConstraints.length > 0) {
sections.push('**Must:**');
for (const c of mustConstraints) {
sections.push(`- ${c.description}`);
}
sections.push('');
}
if (mustNotConstraints.length > 0) {
sections.push('**Must Not:**');
for (const c of mustNotConstraints) {
sections.push(`- ${c.description}`);
}
sections.push('');
}
// Self-Review Section
if (definition.selfReviewPrompts && definition.selfReviewPrompts.length > 0) {
sections.push('## Self-Review (Before Submitting)');
sections.push('');
for (const prompt of definition.selfReviewPrompts) {
sections.push(`- ${prompt}`);
}
sections.push('');
}
return sections.join('\n');
}
/**
* Evaluate success criteria against output
* Returns which criteria passed/failed with details
*/
export function evaluateSuccessCriteria(
role: VerifierRole,
output: string,
context: RoleContext
): {
criterionId: string;
description: string;
passed: boolean;
required: boolean;
message?: string;
}[] {
const definition = ROLE_DEFINITIONS[role];
if (!isIntentBasedRole(definition)) {
return [];
}
const results: {
criterionId: string;
description: string;
passed: boolean;
required: boolean;
message?: string;
}[] = [];
for (const criterion of definition.successCriteria) {
if (criterion.validator) {
const validationResult = criterion.validator(output, context);
results.push({
criterionId: criterion.id,
description: criterion.description,
passed: validationResult.passed,
required: criterion.required,
message: validationResult.message
});
} else {
// No validator - cannot automatically evaluate, assume passed
results.push({
criterionId: criterion.id,
description: criterion.description,
passed: true,
required: criterion.required,
message: 'No automated validator - requires manual review'
});
}
}
return results;
}