home / skills / mgd34msu / goodvibes-plugin / config-hygiene
/plugins/goodvibes/skills/common/review/config-hygiene
npx playbooks add skill mgd34msu/goodvibes-plugin --skill config-hygieneReview the files below or copy the command above to add this skill to your agents.
---
name: config-hygiene
description: Fixes configuration hygiene issues including gitignore patterns, ESLint config duplication, and hook scripts. Use when encountering backup files in repo, missing gitignore patterns, or config maintenance issues.
---
# Configuration Hygiene Fixes
Fixes for project configuration issues. Clean configs prevent noise, ensure consistency, and improve developer experience.
## Quick Start
1. Identify the config issue (gitignore, ESLint, husky)
2. Apply the recommended fix
3. Verify no unintended side effects
4. Commit config changes
## Priority Matrix
| Issue | Priority | Impact |
|-------|----------|--------|
| Backup files in repo | P2 | Noise, confusion |
| Missing gitignore patterns | P2 | Unwanted files tracked |
| Husky silent failure | P2 | Hidden hook failures |
| ESLint config duplication | P3 | Maintenance burden |
| Permissive lint threshold | P2 | Quality regression |
---
## Workflows
### Backup/Artifact Files in Repository (#35 - 12 occurrences)
**Detection**: Files matching `*.bak`, `*.orig`, `*~` patterns.
**Pattern**: Editor/merge artifacts committed to repo.
**Fix Strategy**: Remove from git, add to gitignore.
```bash
# Remove from git (keep locally)
git rm --cached "*.bak"
git rm --cached "*.orig"
git rm --cached "*~"
# Add to .gitignore
echo "*.bak" >> .gitignore
echo "*.orig" >> .gitignore
echo "*~" >> .gitignore
# Commit
git add .gitignore
git commit -m "chore: ignore backup/artifact files"
```
---
### Missing .gitignore Patterns (#36 - 8 occurrences)
**Detection**: Common patterns not in .gitignore.
**Fix Strategy**: Add comprehensive patterns.
```gitignore
# Editor artifacts
*.swp
*.swo
*~
*.bak
*.orig
# OS files
.DS_Store
Thumbs.db
# IDE
.idea/
.vscode/
*.sublime-*
# Build outputs
dist/
build/
*.tsbuildinfo
# Dependencies
node_modules/
# Environment
.env
.env.*
!.env.example
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Test coverage
coverage/
.nyc_output/
# Cache
.cache/
.eslintcache
.parcel-cache/
```
**Template**: See `templates/gitignore-node.txt` for complete Node.js template.
---
### Husky Prepare Script Silent Failure (#37 - 1 occurrence)
**Detection**: `prepare` script uses `|| true` fallback.
**Pattern**: Hook installation fails silently.
```json
// PROBLEM - hides failures
{
"scripts": {
"prepare": "husky install || true"
}
}
```
**Fix Strategy 1**: Conditional execution based on CI.
```json
// SOLUTION - check CI environment
{
"scripts": {
"prepare": "node -e \"if (process.env.CI !== 'true') require('husky').install()\""
}
}
```
**Fix Strategy 2**: Use is-ci package.
```bash
npm install --save-dev is-ci
```
```json
{
"scripts": {
"prepare": "is-ci || husky install"
}
}
```
**Fix Strategy 3**: Husky v9+ auto-setup.
```json
// Husky v9 uses .husky/_/husky.sh automatically
{
"scripts": {
"prepare": "husky"
}
}
```
---
### Permissive lint:strict Threshold (#38 - 1 occurrence)
**Detection**: High `--max-warnings` value in lint script.
**Pattern**: Too many warnings allowed.
```json
// PROBLEM - too permissive
{
"scripts": {
"lint:strict": "eslint . --max-warnings 500"
}
}
```
**Fix Strategy**: Progressive reduction.
```json
// Week 1: Lower to current count + small buffer
{
"scripts": {
"lint:strict": "eslint . --max-warnings 350"
}
}
// Week 2: Lower further
{
"scripts": {
"lint:strict": "eslint . --max-warnings 200"
}
}
// Target: Zero tolerance
{
"scripts": {
"lint:strict": "eslint . --max-warnings 0"
}
}
```
**Tracking Progress**:
```bash
# Get current warning count
npx eslint . 2>&1 | tail -1
# Output: "X warnings" or "X problems (Y errors, Z warnings)"
```
---
### ESLint Config Duplication (#39, #40)
**Issue #39**: Repeated plugin/globals definitions.
```typescript
// PROBLEM - repeated in multiple blocks
export default [
{
files: ['**/*.ts'],
plugins: { '@typescript-eslint': tsPlugin },
},
{
files: ['**/*.tsx'],
plugins: { '@typescript-eslint': tsPlugin }, // Duplicated
},
];
```
**Fix Strategy**: Extract shared config.
```typescript
// SOLUTION - shared base config
const baseTypescriptConfig = {
plugins: {
'@typescript-eslint': tsPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
},
},
};
export default [
{
files: ['**/*.ts'],
...baseTypescriptConfig,
rules: { /* ts-specific rules */ },
},
{
files: ['**/*.tsx'],
...baseTypescriptConfig,
rules: { /* tsx-specific rules */ },
},
];
```
**Issue #40**: Disabled rules duplicated across blocks.
```typescript
// PROBLEM - duplicated disabled rules
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
},
},
```
**Fix Strategy**: Extract to named constant with documentation.
```typescript
// SOLUTION - extracted with docs
/**
* Rules disabled during migration to strict TypeScript.
* TODO: Re-enable progressively - see issue #456
*/
const migrationDisabledRules = {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
} as const;
export default [
{
files: ['**/*.ts'],
rules: {
...migrationDisabledRules,
// ts-specific rules
},
},
];
```
---
### Recursive Directory Walking Without Limits (#23 - 1 occurrence)
**Pattern**: Unbounded recursion in file walking.
```typescript
// PROBLEM - no limits
async function walkDir(dir: string): Promise<string[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
if (entry.isDirectory()) {
files.push(...await walkDir(path.join(dir, entry.name))); // No limit!
}
}
return files;
}
```
**Fix Strategy**: Add depth limit and visited tracking.
```typescript
// SOLUTION - with limits
interface WalkOptions {
maxDepth?: number;
maxFiles?: number;
}
async function walkDir(
dir: string,
options: WalkOptions = {},
depth = 0,
visited = new Set<string>()
): Promise<string[]> {
const { maxDepth = 10, maxFiles = 10000 } = options;
if (depth > maxDepth) {
return [];
}
const realPath = await fs.realpath(dir);
if (visited.has(realPath)) {
return []; // Circular symlink
}
visited.add(realPath);
const entries = await fs.readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
if (files.length >= maxFiles) break;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...await walkDir(fullPath, options, depth + 1, visited));
} else {
files.push(fullPath);
}
}
return files;
}
```
---
### Regex Created Per-Match (#24 - 1 occurrence)
**Pattern**: Regex compiled on every function call.
```typescript
// PROBLEM - regex created every call
function containsKeyword(text: string, keyword: string): boolean {
const regex = new RegExp(`\\b${keyword}\\b`, 'i');
return regex.test(text);
}
```
**Fix Strategy**: Pre-compile and cache.
```typescript
// SOLUTION - cached regex
const keywordRegexCache = new Map<string, RegExp>();
function getKeywordRegex(keyword: string): RegExp {
let regex = keywordRegexCache.get(keyword);
if (!regex) {
regex = new RegExp(`\\b${escapeRegex(keyword)}\\b`, 'i');
keywordRegexCache.set(keyword, regex);
}
return regex;
}
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function containsKeyword(text: string, keyword: string): boolean {
return getKeywordRegex(keyword).test(text);
}
```
---
## Scripts
### Check Config Hygiene
```bash
node scripts/check-config.js
```
### Fix Gitignore
```bash
node scripts/fix-gitignore.js
```
---
## Templates
### Node.js .gitignore
See `templates/gitignore-node.txt`
### ESLint Base Config
See `templates/eslint-base.js`
---
## CI/CD Considerations
### Ensuring Hooks Run
```yaml
# GitHub Actions
- name: Install dependencies
run: npm ci
# This runs "prepare" script which installs husky
# Make sure CI=true is set to skip if needed
```
### Lint in CI
```yaml
- name: Lint
run: npm run lint:strict
# Fails if warnings exceed threshold
```
### Progressive Strictness
```yaml
# Allow gradual improvement
- name: Check lint progress
run: |
WARNINGS=$(npx eslint . 2>&1 | grep -oP '\d+ warning' | grep -oP '\d+' || echo "0")
THRESHOLD=200
if [ "$WARNINGS" -gt "$THRESHOLD" ]; then
echo "Too many warnings: $WARNINGS (max: $THRESHOLD)"
exit 1
fi
```