← 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
}