UNPKG

@jmkim85/dev-flow-mcp

Version:

MCP-based Dev Flow - AI-powered development workflow management with 13 essential tools for TDD and context management

145 lines (137 loc) 5.55 kB
/** * Safety Guards Module * Provides path validation, file header management, and missing file feedback */ import { promises as fs } from 'fs'; import { join } from 'path'; // Find outputs created for a specific stage with new task-based structure export async function findStageOutputs(stage, taskId, projectRoot) { const outputs = []; if (!taskId) { console.error('❌ No task ID provided for stage output check'); return outputs; } try { // NEW: Task-based folder structure const taskDir = join(projectRoot, '.devflow', 'tasks', taskId); // Define expected outputs for each stage with new structure const stageOutputMap = { 'analyze': [`analysis/analysis.md`, `analysis/requirements.md`], 'design': [`design/architecture.md`, `design/api_design.md`], 'write_tests': [`tests/test_plan.md`, `tests/test_cases.md`], 'implement': [`implementation/implementation.md`, `implementation/code_review.md`], 'refactor': [`refactor/refactor_plan.md`, `refactor/improvements.md`] }; const expectedOutputs = stageOutputMap[stage] || []; // Check if each expected output exists in task directory for (const output of expectedOutputs) { try { const outputPath = join(taskDir, output); await fs.access(outputPath); outputs.push(output); console.error(`✅ Found stage output: ${output} in task ${taskId}`); } catch { console.error(`❌ Missing stage output: ${output} in task ${taskId}`); } } // Also check for actual test files if stage is write_tests if (stage === 'write_tests') { try { const testDirs = ['test', 'tests', '__tests__', 'spec']; for (const testDir of testDirs) { try { const testDirPath = join(projectRoot, testDir); const stats = await fs.stat(testDirPath); if (stats.isDirectory()) { const testFiles = await fs.readdir(testDirPath); if (testFiles.length > 0) { outputs.push('test files'); console.error(`✅ Found test files in ${testDir}/`); break; } } } catch { // Continue checking other test directories } } } catch { // No test files found } } } catch (error) { console.error(`Warning: Could not check stage outputs: ${error}`); } return outputs; } // 안전장치 3-2: 경로 화이트리스트 검증 export function validateTaskPath(filePath, taskId) { const allowedPaths = [ `.devflow/tasks/${taskId}/**`, `.devflow/project/**`, `.devflow/workflows/**` ]; // Normalize path separators const normalizedPath = filePath.replace(/\\/g, '/'); // Check if path matches any allowed pattern for (const pattern of allowedPaths) { const regexPattern = pattern .replace(/\*\*/g, '.*') .replace(/\*/g, '[^/]*') .replace(/\./g, '\\.'); const regex = new RegExp(`^${regexPattern}$`); if (regex.test(normalizedPath)) { return { valid: true }; } } return { valid: false, reason: `Path '${filePath}' is not allowed. Use task-specific paths: .devflow/tasks/${taskId}/` }; } // 안전장치 3-3: 헤더 자동 보정 export function ensureFileHeader(content, filePath) { const headerPattern = /^#\s+file:\s+/; if (!headerPattern.test(content)) { return `# file: ${filePath}\n\n${content}`; } return content; } // 안전장치 3-4: 누락 파일 피드백 생성 export async function generateMissingFilesFeedback(stage, taskId, projectRoot) { const outputsCreated = await findStageOutputs(stage, taskId, projectRoot); const stageOutputMap = { 'analyze': [`analysis/analysis.md`, `analysis/requirements.md`], 'design': [`design/architecture.md`, `design/api_design.md`], 'write_tests': [`tests/test_plan.md`, `tests/test_cases.md`], 'implement': [`implementation/implementation.md`, `implementation/code_review.md`], 'refactor': [`refactor/refactor_plan.md`, `refactor/improvements.md`] }; const expectedOutputs = stageOutputMap[stage] || []; const missingFiles = expectedOutputs.filter(file => !outputsCreated.includes(file)); if (missingFiles.length === 0) { return ''; } const taskDir = `.devflow/tasks/${taskId}`; const missingPaths = missingFiles.map(file => `${taskDir}/${file}`); return ` 📝 **Missing Required Files** Please create the following files before advancing to the next stage: ${missingPaths.map(path => `- \`${path}\``).join('\n')} **Example for analysis stage:** \`\`\`markdown # file: .devflow/tasks/${taskId}/analysis/analysis.md # Analysis for Task ${taskId} ## Problem Statement [Describe the problem to solve] ## Requirements [List functional and non-functional requirements] ## Approach [Outline your solution approach] \`\`\` Create these files and try advancing again.`; } //# sourceMappingURL=safety-guards.js.map