← back to ClawCoder

src/lib/analyzer/suggester.ts

216 lines

import type { AnalysisResult, Suggestion } from './types'

/**
 * Determine priority based on criterion weight.
 */
function priorityFromWeight(weight: number): 'high' | 'medium' | 'low' {
  if (weight >= 10) return 'high'
  if (weight >= 5) return 'medium'
  return 'low'
}

let suggestionCounter = 0

function nextId(criterion: string): string {
  suggestionCounter++
  return `suggestion-${criterion}-${suggestionCounter}`
}

/**
 * Generate improvement suggestions based on the analysis result.
 * Only generates suggestions for criteria that scored below their maximum.
 */
export function generateSuggestions(
  content: string,
  analysis: AnalysisResult
): Suggestion[] {
  // Reset counter for deterministic ids within a single call
  suggestionCounter = 0

  const suggestions: Suggestion[] = []

  for (const criterion of analysis.criteria) {
    // Skip criteria that already have full marks
    if (criterion.score === criterion.maxScore) continue

    const priority = priorityFromWeight(criterion.weight)

    switch (criterion.id) {
      case 'line-count': {
        if (analysis.lineCount > 200) {
          suggestions.push({
            id: nextId(criterion.id),
            criterion: criterion.id,
            description:
              'File exceeds 200 lines. Split detailed rules into .claude/rules/ files and reference them from CLAUDE.md.',
            before: 'missing',
            after:
              '## Additional Rules\nSee .claude/rules/ for detailed configuration:\n- .claude/rules/code-style.md\n- .claude/rules/safety.md\n- .claude/rules/workflow.md\n',
            priority,
          })
        }
        break
      }

      case 'structure': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add more ## section headers to organize your CLAUDE.md. Recommended sections: Workflow, Safety, Commands, Code Style.',
          before: 'missing',
          after:
            '## Workflow\n- Make small, reviewable changes\n- Run tests after behavior changes\n\n## Safety\n- Never force push or reset hard without confirmation\n- Never modify .env files directly\n',
          priority,
        })
        break
      }

      case 'specificity': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add specific tool commands so Claude knows exactly how to build, test, and lint your project.',
          before: 'missing',
          after:
            '## Commands\n- Install: npm install\n- Dev: npm run dev\n- Test: npm test\n- Lint: npm run lint\n- Build: npm run build\n',
          priority,
        })
        break
      }

      case 'safety-rules': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add a dedicated ## Safety section with explicit constraints on destructive operations.',
          before: 'missing',
          after:
            '## Safety\n- Don\'t delete or modify .env, credentials, or secret files\n- Never force push or reset hard without asking\n- Don\'t run destructive database commands without confirmation\n- Always backup before bulk operations\n',
          priority,
        })
        break
      }

      case 'build-commands': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add labeled build commands (install:, dev:, test:, lint:, build:) so Claude knows the project toolchain.',
          before: 'missing',
          after:
            '## Commands\n- Install: npm install\n- Dev: npm run dev\n- Test: npm test\n- Lint: npm run lint\n- Build: npm run build\n- Format: npm run format\n',
          priority,
        })
        break
      }

      case 'code-style': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add code style guidance or reference a .claude/rules/code-style.md file.',
          before: 'missing',
          after:
            '## Code Style\n- Follow the project\'s ESLint and Prettier configuration\n- Use consistent naming conventions (camelCase for variables, PascalCase for components)\n- See .claude/rules/code-style.md for detailed rules\n',
          priority,
        })
        break
      }

      case 'modular-imports': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add references to modular documentation (@docs/) and .claude/rules/ files to keep CLAUDE.md lean.',
          before: 'missing',
          after:
            '## Architecture\n@docs/ARCHITECTURE.md\n\n## Rules\nSee .claude/rules/ for detailed configuration.\n',
          priority,
        })
        break
      }

      case 'no-secrets': {
        const secretFindings = criterion.findings.filter(
          f => f.severity === 'critical'
        )
        for (const finding of secretFindings) {
          suggestions.push({
            id: nextId(criterion.id),
            criterion: criterion.id,
            description: `Remove secret from line ${finding.line ?? '?'}. Use environment variables instead.`,
            before: finding.message,
            after:
              '# Use environment variables for secrets\n# API_KEY=$API_KEY (set in .env, never commit)\n',
            priority: 'high',
          })
        }
        break
      }

      case 'no-bloat': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Reduce bloat: remove excessive empty lines, duplicate sections, and overly long lines.',
          before: 'excessive whitespace or duplicates',
          after: '(clean up empty lines and remove duplicate ## headers)',
          priority,
        })
        break
      }

      case 'skills-subagents': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Consider adding skill or subagent references if your workflow benefits from task delegation.',
          before: 'missing',
          after:
            '## Agents\nSee .claude/agents/ for subagent definitions.\n',
          priority,
        })
        break
      }

      case 'hook-enforcement': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Add hook references to automate linting/testing on file changes via .claude/settings.json.',
          before: 'missing',
          after:
            '## Hooks\nConfigured in .claude/settings.json — auto-runs lint on file save and tests before commit.\n',
          priority,
        })
        break
      }

      case 'mcp-config': {
        suggestions.push({
          id: nextId(criterion.id),
          criterion: criterion.id,
          description:
            'Reference MCP server configuration if you use external tool integrations.',
          before: 'missing',
          after:
            '## MCP Servers\nConfigured in .mcp.json — see project docs for available MCP integrations.\n',
          priority,
        })
        break
      }
    }
  }

  return suggestions
}