Implement StructureAnalyzerService - Analyze project directory structures
Migrated from llm/agent-buildkit#2 on 2025-10-12T20:16:22.549Z Original author: @group_286_bot_4df652bbc58e62e505f1a777cd8f21b8 | Created: 2025-10-11T03:55:10.468Z
StructureAnalyzerService Implementation
Parent Issue
Related to #1 (Project Health Auditor Epic)
Objective
Implement the StructureAnalyzerService that analyzes project directory structures, detects violations of standard patterns, and identifies duplicate folder patterns.
Contract (Port Interface)
Implements: `IStructureAnalyzerPort` from `/src/services/ports/project-health.port.ts`
Required Methods
```typescript interface IStructureAnalyzerPort { analyzeStructure(projectPath: string, maxDepth?: number): Promise; findDuplicateFolders(projectPath: string): Promise; checkStandardCompliance(projectPath: string): Promise; } ```
Responsibilities (SINGLE RESPONSIBILITY)
ONLY analyze project directory structures. Does NOT:
- Calculate health scores (HealthScoringService)
- Audit scripts (ScriptAuditorService)
- Perform cleanup (CleanupService)
Implementation Details
File Location
`/src/services/domain/project-health/StructureAnalyzerService.ts`
Standard Architecture Patterns
Must detect compliance with two patterns:
Pattern A: Full-Stack
``` project/ ├── backend/ │ ├── src/ │ └── tests/ ├── frontend/ │ ├── src/ │ └── tests/ ├── docs/ ├── infrastructure/ └── openapi/ ```
Pattern B: Backend-Only
``` project/ ├── src/ ├── tests/ ├── docs/ ├── infrastructure/ └── openapi/ ```
Violations to Detect
- Conflicting structure: Both `backend/` and `src/` at root
- Missing directories: No `tests/`, `docs/`, or `openapi/`
- Duplicate folders: Multiple `cli/`, `src/`, `api/`, `bin/` folders at different depths
- Anti-patterns: `scripts/` folder at root
Algorithm: analyzeStructure()
```typescript async analyzeStructure(projectPath: string, maxDepth = 10): Promise { // 1. Build directory tree (recursive scan up to maxDepth) const tree = await this.buildDirectoryTree(projectPath, maxDepth);
// 2. Check for standard compliance const compliance = await this.checkCompliance(projectPath);
// 3. Detect violations const violations: StructureViolation[] = [];
// Check for backend + src conflict if (await exists(join(projectPath, 'backend')) && await exists(join(projectPath, 'src'))) { violations.push({ type: 'conflicting_structure', path: projectPath, issue: 'Project has both backend/ and src/ at root - violates standard patterns' }); }
// Check for missing standard directories if (!await exists(join(projectPath, 'tests'))) { violations.push({ type: 'missing_directory', path: 'tests/', issue: 'Missing tests/ directory' }); }
// Check for anti-patterns if (await exists(join(projectPath, 'scripts'))) { violations.push({ type: 'anti_pattern', path: 'scripts/', issue: 'scripts/ folder at root is an anti-pattern (use npm scripts or TypeScript)' }); }
return { projectName: basename(projectPath), directoryTree: tree, violations, standardCompliance: compliance }; } ```
Algorithm: findDuplicateFolders()
```typescript async findDuplicateFolders(projectPath: string): Promise { const folderTypes = ['cli', 'src', 'api', 'bin', 'lib', 'components', 'services', 'utils']; const duplicates: DuplicateFolderGroup[] = [];
for (const type of folderTypes) { // Find all folders matching this type const locations = await this.findFoldersByName(projectPath, type);
// If more than one location found, it's a duplicate
if (locations.length > 1) {
duplicates.push({
type,
locations: locations.map(loc => ({
path: loc,
depth: this.calculateDepth(projectPath, loc)
}))
});
}
}
return { projectName: basename(projectPath), duplicates }; } ```
Dependencies
- IFileSystemPort: For directory traversal (inject via constructor)
- ProjectDiscoveryService: Reference implementation pattern
- Zod schemas: StructureAnalysisSchema, DuplicateFoldersReportSchema
Acceptance Criteria
-
Implements all 3 methods from IStructureAnalyzerPort -
Constructor accepts IFileSystemPort for testability -
Detects all 4 violation types (conflicting structure, missing dirs, duplicates, anti-patterns) -
Respects maxDepth parameter (doesn't scan too deep) -
Returns data matching Zod schemas -
Handles errors gracefully (permissions, missing dirs) -
Unit tests with mock filesystem (>90% coverage) -
Integration test with real project directories
Testing Strategy
```typescript describe('StructureAnalyzerService', () => { let service: StructureAnalyzerService; let mockFs: MockFileSystem;
beforeEach(() => { mockFs = new MockFileSystem(); service = new StructureAnalyzerService(mockFs); });
it('detects conflicting structure (backend + src)', async () => { mockFs.setupDirectory('/project', { 'backend/': {}, 'src/': {} });
const result = await service.analyzeStructure('/project');
expect(result.violations).toContainEqual({
type: 'conflicting_structure',
path: '/project',
issue: expect.stringContaining('backend/ and src/')
});
});
it('detects duplicate cli folders', async () => { mockFs.setupDirectory('/project', { 'cli/': {}, 'src/cli/': {}, 'backend/cli/': {} });
const result = await service.findDuplicateFolders('/project');
expect(result.duplicates).toContainEqual({
type: 'cli',
locations: expect.arrayContaining([
{ path: '/project/cli', depth: 0 },
{ path: '/project/src/cli', depth: 1 },
{ path: '/project/backend/cli', depth: 1 }
])
});
}); }); ```
Estimated Effort
8 hours
- Implementation: 4 hours
- Tests: 3 hours
- Documentation: 1 hour
References
- Port: `/src/services/ports/project-health.port.ts:IStructureAnalyzerPort`
- Reference: `/src/services/domain/project-health/ProjectDiscoveryService.ts` (follow this pattern)
- Schemas: `/src/types/dto/project-health.schemas.ts` (StructureAnalysisSchema, DuplicateFoldersReportSchema)
- Standards: `~/.claude/CLAUDE.md` (PROJECT STRUCTURE STANDARD section)
Labels
`enhancement`, `service-implementation`, `architecture`, `health-monitoring`