overseer.check_compliance
Validate repository structure against sentinel.yml conventions to ensure compliance with expected directories and key files.
Instructions
Validates repository structure against sentinel.yml conventions. Checks for expected directories and key files.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| repo_root | Yes | Root path of the repository | |
| phase_id | No | Optional: Specific phase ID to check | |
| strict | No | If true, all checks must pass. If false, warns about missing items. |
Implementation Reference
- src/tools/check-compliance.ts:34-228 (handler)The core handler function implementing the overseer.check_compliance tool logic. It validates the repository against conventions from sentinel.yml, checking directories, key files, PHASES.md, phase-specific structures, and naming.export async function handleCheckCompliance( args: { repo_root: string; phase_id?: string; strict?: boolean; }, phaseManager: PhaseManager ): Promise<{ success: boolean; compliant: boolean; phase_id?: string; checks: Array<{ check_type: 'directory' | 'file' | 'convention' | 'phase_structure'; passed: boolean; message: string; details?: Record<string, unknown>; }>; summary: { total_checks: number; passed: number; failed: number; }; }> { const checks: Array<{ check_type: 'directory' | 'file' | 'convention' | 'phase_structure'; passed: boolean; message: string; details?: Record<string, unknown>; }> = []; try { // Resolve repo path let repoPath = args.repo_root; if (!repoPath.startsWith('/')) { repoPath = join(homedir(), 'dev', repoPath); } repoPath = FSUtils.expandPath(repoPath); if (!FSUtils.dirExists(repoPath)) { return { success: false, compliant: false, checks: [{ check_type: 'directory', passed: false, message: 'Repository directory does not exist', }], summary: { total_checks: 1, passed: 0, failed: 1, }, }; } // Load config to get conventions const configLoader = new ConfigLoader(); const config = configLoader.getConfig(); const conventions = config.conventions; // Check PHASES.md exists const phasesPath = join(repoPath, conventions.phases_index.file); if (FSUtils.fileExists(phasesPath)) { checks.push({ check_type: 'file', passed: true, message: `${conventions.phases_index.file} exists`, }); } else { checks.push({ check_type: 'file', passed: false, message: `${conventions.phases_index.file} is missing`, }); } // Check for common expected directories based on conventions const expectedDirs = ['src', 'config', 'docs']; for (const dir of expectedDirs) { const dirPath = join(repoPath, dir); if (FSUtils.dirExists(dirPath)) { checks.push({ check_type: 'directory', passed: true, message: `${dir}/ directory exists`, }); } else { checks.push({ check_type: 'directory', passed: false, message: `${dir}/ directory is missing`, }); } } // Check for key files const keyFiles = ['README.md', 'package.json', '.gitignore']; for (const file of keyFiles) { const filePath = join(repoPath, file); if (FSUtils.fileExists(filePath)) { checks.push({ check_type: 'file', passed: true, message: `${file} exists`, }); } else { checks.push({ check_type: 'file', passed: false, message: `${file} is missing`, }); } } // If phase_id provided, check phase-specific structure if (args.phase_id) { const phaseId = args.phase_id.padStart(2, '0'); const phaseFilePath = join(repoPath, `PHASE-${phaseId}.md`); if (FSUtils.fileExists(phaseFilePath)) { const phaseContent = FSUtils.readFile(phaseFilePath); // Check for required sections const requiredSections = ['Description', 'Deliverables', 'Done Criteria', 'Progress']; for (const section of requiredSections) { if (phaseContent.includes(`## ${section}`)) { checks.push({ check_type: 'phase_structure', passed: true, message: `PHASE-${phaseId}.md has ${section} section`, }); } else { checks.push({ check_type: 'phase_structure', passed: false, message: `PHASE-${phaseId}.md missing ${section} section`, }); } } } else { checks.push({ check_type: 'phase_structure', passed: false, message: `PHASE-${phaseId}.md file is missing`, }); } } // Check naming conventions const repoName = repoPath.split('/').pop() || ''; const namingConvention = conventions.naming.phase_names; // Use phase_names as proxy for repo naming if (namingConvention === 'kebab-case') { const isValidKebabCase = /^[a-z0-9]+(-[a-z0-9]+)*$/.test(repoName); checks.push({ check_type: 'convention', passed: isValidKebabCase, message: `Repository name follows kebab-case convention`, details: { repo_name: repoName, expected_format: 'kebab-case' }, }); } // Calculate summary const passed = checks.filter(c => c.passed).length; const failed = checks.filter(c => !c.passed).length; const compliant = args.strict ? failed === 0 : true; // In strict mode, all must pass return { success: true, compliant, phase_id: args.phase_id, checks, summary: { total_checks: checks.length, passed, failed, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, compliant: false, checks: [{ check_type: 'convention', passed: false, message: `Error checking compliance: ${errorMessage}`, }], summary: { total_checks: 1, passed: 0, failed: 1, }, }; } }
- src/tools/check-compliance.ts:8-32 (schema)Defines the tool specification including name, description, and detailed inputSchema for parameters repo_root (required), phase_id (optional), strict (optional boolean).export function createCheckComplianceTool(phaseManager: PhaseManager): Tool { return { name: 'overseer.check_compliance', description: 'Validates repository structure against sentinel.yml conventions. Checks for expected directories and key files.', inputSchema: { type: 'object', required: ['repo_root'], properties: { repo_root: { type: 'string', description: 'Root path of the repository', }, phase_id: { type: 'string', description: 'Optional: Specific phase ID to check', }, strict: { type: 'boolean', default: false, description: 'If true, all checks must pass. If false, warns about missing items.', }, }, }, }; }
- src/tools/index.ts:22-41 (registration)Tool factory function that registers the overseer.check_compliance tool by calling createCheckComplianceTool and including it in the array of available tools.export function createTools(context: ToolContext): Tool[] { return [ // Planning tools createPlanProjectTool(context.phaseManager), createInferPhasesTool(context.configLoader), createUpdatePhasesTool(context.phaseManager), // Execution tools createRunPhaseTool(context.phaseManager), createAdvancePhaseTool(context.phaseManager), createStatusTool(context.phaseManager), // QA tools createLintRepoTool(context.configLoader), createSyncDocsTool(context.phaseManager), createCheckComplianceTool(context.phaseManager), // Environment tools createEnvMapTool(context.phaseManager), createGenerateCiTool(context.phaseManager), createSecretsTemplateTool(context.phaseManager), ]; }
- src/tools/index.ts:43-80 (registration)Central tool dispatcher that routes calls to 'overseer.check_compliance' to the handleCheckCompliance function with context.export async function handleToolCall( name: string, args: any, context: ToolContext ): Promise<any> { switch (name) { // Planning tools case 'overseer.plan_project': return await handlePlanProject(args, context.phaseManager); case 'overseer.infer_phases': return await handleInferPhases(args, context.configLoader); case 'overseer.update_phases': return await handleUpdatePhases(args, context.phaseManager); // Execution tools case 'overseer.run_phase': return await handleRunPhase(args, context.phaseManager); case 'overseer.advance_phase': return await handleAdvancePhase(args, context.phaseManager); case 'overseer.status': return await handleStatus(args, context.phaseManager); // QA tools case 'overseer.lint_repo': return await handleLintRepo(args, context.configLoader); case 'overseer.sync_docs': return await handleSyncDocs(args, context.phaseManager); case 'overseer.check_compliance': return await handleCheckCompliance(args, context.phaseManager); // Environment tools case 'overseer.env_map': return await handleEnvMap(args, context.phaseManager); case 'overseer.generate_ci': return await handleGenerateCi(args, context.phaseManager); case 'overseer.secrets_template': return await handleSecretsTemplate(args, context.phaseManager); default: throw new Error(`Unknown tool: ${name}`); } }