#!/usr/bin/env node /** * Format Locker - Enforce Structured Output Templates * * Ensures responses follow prescribed formats based on workflow type */ class FormatLocker { constructor(workflow) { this.workflow = workflow; this.templates = this.loadTemplates(); } /** * Load format templates for each workflow type */ loadTemplates() { return { coding: { requiredSections: ['Summary', 'Files Modified', 'Verification', 'Decisions Made'], optionalSections: ['Still Open', 'Testing Notes'], format: ` ## Summary [What changed - 1-2 sentences] ## Files Modified | File | Change | |------|--------| | file1.py | [description] | | file2.js | [description] | ## Verification - [ ] Tests pass: \`command\` - [ ] Lint passes: \`command\` - [ ] Manual verification: [how] ## Decisions Made - [Decision 1] - [Decision 2] ## Still Open - [ ] [if any] `.trim(), validators: { 'Files Modified': (content) => content.includes('|') && content.includes('File'), 'Verification': (content) => content.includes('- [ ]') || content.includes('- [x]'), 'Decisions Made': (content) => content.startsWith('- ') } }, debug: { requiredSections: ['Problem', 'Cause', 'Fix', 'Validation'], optionalSections: ['Prevention', 'Impact'], format: ` ## Problem [Symptom - what was observed] ## Cause [Root cause - why it happened] ## Fix [What was changed to resolve it] ## Validation [How it was verified fixed] `.trim(), validators: { 'Problem': (content) => content.length > 10, 'Cause': (content) => content.length > 10, 'Fix': (content) => content.length > 10, 'Validation': (content) => content.length > 10 } }, deploy: { requiredSections: ['Deployment Summary', 'Pre-Deployment', 'Changes Applied', 'Verification'], optionalSections: ['Rollback', 'Post-Deployment Notes'], format: ` ## Deployment Summary [What and where] ## Pre-Deployment - [x] Backup created - [x] Tests pass - [x] Rollback plan documented ## Changes Applied | Step | Command | Status | |------|---------|--------| | 1 | [command] | ✅ | | 2 | [command] | ✅ | ## Verification - [x] Service responding: [url] - [x] Logs normal: [check] - [x] Smoke test: [result] `.trim(), validators: { 'Pre-Deployment': (content) => content.includes('- [x]') || content.includes('- [ ]'), 'Changes Applied': (content) => content.includes('|'), 'Verification': (content) => content.includes('- [x]') || content.includes('- [ ]') } }, audit: { requiredSections: ['Scope', 'Findings', 'Recommendations'], optionalSections: ['Risk Assessment', 'Priority Matrix', 'Action Items'], format: ` ## Scope [What was audited] ## Findings | Item | Status | Severity | |------|--------|----------| | Finding 1 | ✅/⚠️/❌ | Low/Med/High | | Finding 2 | ✅/⚠️/❌ | Low/Med/High | ## Recommendations 1. **[Priority]** [Action to take] 2. **[Priority]** [Action to take] `.trim(), validators: { 'Findings': (content) => content.includes('|'), 'Recommendations': (content) => /^\d+\./.test(content) } }, planning: { requiredSections: ['Goal', 'Approach', 'Breakdown', 'Timeline'], optionalSections: ['Dependencies', 'Risks', 'Acceptance Criteria'], format: ` ## Goal [What we're trying to achieve] ## Approach [High-level strategy] ## Breakdown | Task | Owner | Estimate | |------|-------|----------| | Task 1 | [agent] | [time] | | Task 2 | [agent] | [time] | ## Timeline - Phase 1: [duration] - [deliverable] - Phase 2: [duration] - [deliverable] `.trim(), validators: { 'Breakdown': (content) => content.includes('|'), 'Timeline': (content) => content.includes('- Phase') } } }; } /** * Get template for current workflow */ getTemplate() { return this.templates[this.workflow] || this.templates['coding']; } /** * Validate that response follows required format */ validateFormat(response) { const template = this.getTemplate(); const results = { passed: true, missingSections: [], malformedSections: [], suggestions: [] }; // Check for required sections for (const section of template.requiredSections) { const sectionPattern = new RegExp(`##\\s*${section.replace(/\s+/g, '\\s+')}`, 'i'); if (!sectionPattern.test(response)) { results.missingSections.push(section); results.passed = false; } } // Extract and validate section contents const sections = this.extractSections(response); for (const [name, content] of Object.entries(sections)) { if (template.validators[name]) { const isValid = template.validators[name](content); if (!isValid) { results.malformedSections.push(name); results.passed = false; } } } // Generate suggestions if (results.missingSections.length > 0) { results.suggestions.push(`Add missing sections: ${results.missingSections.join(', ')}`); } if (results.malformedSections.length > 0) { results.suggestions.push(`Fix format in sections: ${results.malformedSections.join(', ')}`); } return results; } /** * Extract sections from markdown response */ extractSections(response) { const sections = {}; const sectionRegex = /^##\s+(.+)$/gm; let match; const sectionNames = []; const sectionPositions = []; while ((match = sectionRegex.exec(response)) !== null) { sectionNames.push(match[1].trim()); sectionPositions.push(match.index); } // Extract content between sections for (let i = 0; i < sectionNames.length; i++) { const startPos = sectionPositions[i] + sectionNames[i].length + 3; // +3 for "## " const endPos = i < sectionNames.length - 1 ? sectionPositions[i + 1] : response.length; sections[sectionNames[i]] = response.substring(startPos, endPos).trim(); } return sections; } /** * Apply format template to content */ applyTemplate(content, customData = {}) { const template = this.getTemplate(); let formatted = template.format; // Replace placeholders with actual content for (const [key, value] of Object.entries(content)) { const placeholder = `[${key}]`; if (formatted.includes(placeholder)) { formatted = formatted.replace(placeholder, value); } } // Replace custom data for (const [key, value] of Object.entries(customData)) { const placeholder = `{${key}}`; if (formatted.includes(placeholder)) { formatted = formatted.replace(new RegExp(`\\{${key}\\}`, 'g'), value); } } return formatted; } /** * Wrap a response to ensure format compliance */ enforceFormat(response, autoFix = true) { const validation = this.validateFormat(response); if (validation.passed) { return { response: response, status: 'passed', fixes: [] }; } if (!autoFix) { return { response: response, status: 'failed', errors: validation, suggestion: this.getTemplate().format }; } // Auto-fix: add missing sections let fixed = response; const sections = this.extractSections(response); const template = this.getTemplate(); for (const section of validation.missingSections) { // Add missing section at end fixed += `\n\n## ${section}\n[To be completed]`; } return { response: fixed, status: 'fixed', fixes: validation.suggestions }; } /** * Get format requirements as system prompt addition */ getFormatPrompt() { const template = this.getTemplate(); return ` ## Output Format Requirements You MUST follow this structure exactly: ${template.format} ### Required Sections ${template.requiredSections.map(s => `- ${s}`).join('\n')} ### Rules - All required sections must be present - Use proper markdown tables where shown - Include checkboxes for verification items - Be specific, not vague `; } } // Export for use module.exports = FormatLocker; // CLI usage if (require.main === module) { const workflow = process.argv[2] || 'coding'; const responseFile = process.argv[3]; const locker = new FormatLocker(workflow); console.log('=== Format Template ==='); console.log(locker.getTemplate().format); if (responseFile) { const fs = require('fs'); const response = fs.readFileSync(responseFile, 'utf8'); console.log('\n=== Validation ==='); const result = locker.enforceFormat(response, true); console.log('Status:', result.status); if (result.fixes) { console.log('Fixes:', result.fixes); } } }