feat: ClaudeForge 2.1.0 — installable plugin with 150-line cap, forked audit skills, /sync --weekly, and AGENTS.md export (#26)

This commit is contained in:
Alireza Rezvani
2026-05-19 04:07:05 +02:00
committed by GitHub
parent ffff0fc4c3
commit 032c5e5a0f
35 changed files with 1740 additions and 629 deletions
+85
View File
@@ -0,0 +1,85 @@
> Parent context: see the root [CLAUDE.md](../CLAUDE.md) for project-wide guidelines and behavioural rules.
> Chained import: `@../CLAUDE.md`
# Skill Development
Guidelines for the `claudeforge-skill` Python modules and the `karpathy-guidelines` skill.
## Component Interaction Flow
```
User Project
/enhance-claude-md (Slash Command) → Explore subagent (deep scan)
[Discovery] → [Analysis] → [Task]
claude-md-guardian (Agent) OR Direct Skill Invocation
claudeforge-skill (Python Modules)
workflow.py → analyzer.py → validator.py → template_selector.py → generator.py
CLAUDE.md ≤ 150 lines, chained via `@path` imports
```
## Python Module Architecture
Five modules live under `skill/`:
- **`workflow.py`** — `InitializationWorkflow`: orchestrates interactive setup, detects project type / tech stack / team size / phase / workflows, returns a context dict.
- **`analyzer.py`** — `CLAUDEMDAnalyzer`: analyses existing files; quality scoring (0100) across length, completeness, formatting, specificity, modularity.
- **`validator.py`** — `BestPracticesValidator`: checks file length (hard cap 150, warning at 120), required sections, formatting, anti-patterns.
- **`template_selector.py`** — `TemplateSelector`: maps project type + team size to a template; all team-size targets are ≤ 150 lines.
- **`generator.py`** — `ContentGenerator`: writes root + context files, emits `@path` chain imports, prepends sub-file back-links, idempotent `merge_with_existing`. Also exposes `generate_rules_file(name, description, paths, body)` for path-scoped `.claude/rules/*.md` files (loaded lazily by Claude when accessed files match the `paths:` globs) and prepends `@AGENTS.md`-style imports when `project_context['existing_instruction_files']` lists sibling instruction files.
## Required Output Sections
Every generated CLAUDE.md must contain:
- Project structure (ASCII tree, for projects that need it)
- Setup & installation
- Architecture (for non-trivial projects)
- `## Behavioral Guidelines` (Karpathy summary — inserted automatically)
- Cross-check against reference examples in `skill/examples/`
## Modifying Python Modules
1. Edit files in `skill/`.
2. Run the smoke test (see `docs/CLAUDE.md` → Testing & Validation).
3. Re-install for live testing: `./install.sh` (project-level scope).
4. Test slash command: `/enhance-claude-md`.
5. Validate output against `skill/examples/`.
6. Update `CHANGELOG.md`.
## Adding Reference Templates
1. Add a new file under `skill/examples/`.
2. Follow the native format (project structure, setup, architecture, tech guidelines).
3. Update `skill/examples/README.md`.
4. Teach `template_selector.py` how to detect the new template.
5. Add a scenario to `skill/sample_input.json`.
## Quality Scoring (analyzer.py)
`calculate_quality_score()` breakdown:
- length_appropriateness: 25 pts (50150 lines ideal; the 150-line hard cap is enforced here)
- section_completeness: 25 pts (required sections present)
- formatting_quality: 20 pts (markdown, heading hierarchy, code blocks)
- content_specificity: 15 pts (project-specific, not generic)
- modular_organization: 15 pts (chained sub-files when needed)
## Tech Stack Detection
`skill/workflow.py``_detect_tech_stack()` reads these signals:
- **Frontend** — React/Vue/Angular via `package.json`; Angular via `angular.json`; TypeScript via `tsconfig.json`.
- **Backend** — Node (`package.json`), Python (`requirements.txt` / `pyproject.toml` / `setup.py`), Go (`go.mod`), Java (`pom.xml` / `build.gradle`), Rust (`Cargo.toml`).
- **Database** — Postgres (`pg` / `psycopg2`), MongoDB (`mongoose` / `pymongo`), Redis (`redis` / `ioredis`).
Add new detectors there, not in the template selector.
## Karpathy Guidelines Skill
`skill/karpathy-guidelines/SKILL.md` is the standalone skill installed at `~/.claude/skills/karpathy-guidelines/`. Adapted with attribution from the MIT-licensed [forrestchang/andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills) repo. The four principles are inserted automatically into every generated CLAUDE.md via `template_selector._generate_karpathy_guidelines()` and `generator._generate_karpathy_guidelines()`. Do not strip the embedded section during enhancement.
+28 -8
View File
@@ -1,6 +1,25 @@
---
name: claude-md-enhancer
description: Analyzes, generates, and enhances CLAUDE.md files for any project type using best practices, modular architecture support, and tech stack customization. Use when setting up new projects, improving existing CLAUDE.md files, or establishing AI-assisted development standards.
model: haiku
effort: medium
paths:
- "**/CLAUDE.md"
- "**/CLAUDE.local.md"
- "**/AGENTS.md"
- "**/.cursorrules"
- "**/.windsurfrules"
- "**/.claude/rules/*.md"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- "Bash(ls:*)"
- "Bash(find:*)"
- "Bash(git:*)"
- "Bash(wc:*)"
permissions:
allow:
- Read
@@ -8,9 +27,9 @@ permissions:
- Edit
- Glob
- Grep
- Bash(ls:*)
- Bash(find:*)
- Bash(git:*)
- "Bash(ls:*)"
- "Bash(find:*)"
- "Bash(git:*)"
---
# CLAUDE.md File Enhancer
@@ -294,7 +313,7 @@ Analyzes existing CLAUDE.md files to identify structure, sections, and quality i
Validates CLAUDE.md files against best practices and Anthropic guidelines.
**Key Functions**:
- `validate_length()` - Check file length (warn if >300 lines)
- `validate_length()` - Check file length (hard cap: 150 lines; warn from 120)
- `validate_structure()` - Verify required sections present
- `validate_formatting()` - Check markdown formatting quality
- `validate_completeness()` - Ensure critical information included
@@ -367,10 +386,11 @@ Before finalizing any CLAUDE.md generation:
## Template Categories
### By Size
- **Minimal** (50 lines) - Solo developers, prototypes, hackathons
- **Core** (100-150 lines) - Small teams, MVPs, standard projects
- **Detailed** (200-300 lines) - Large teams, production systems, enterprise
### By Size (single CLAUDE.md cap: 150 lines)
- **Minimal** (≤ 75 lines) - Solo developers, prototypes, hackathons
- **Core** (100 lines) - Small teams, MVPs, standard projects
- **Detailed** (≤ 125 lines) - Medium teams, production systems
- **Comprehensive** (≤ 150 lines + modular sub-files) - Large teams, enterprise; spread detail across chained sub-CLAUDE.md files instead of growing the root
### By Project Type
- **Web App** - Frontend-focused (React, Vue, Angular)
+17 -10
View File
@@ -165,17 +165,23 @@ class CLAUDEMDAnalyzer:
issues = []
# Check file length
if self.line_count > 400:
if self.line_count > 250:
issues.append({
"type": "length_critical",
"severity": "high",
"message": f"File is too long ({self.line_count} lines). Recommended: split into modular files."
"message": f"File is too long ({self.line_count} lines). Hard cap is 150; split into modular files."
})
elif self.line_count > 300:
elif self.line_count > 150:
issues.append({
"type": "length_warning",
"severity": "high",
"message": f"File exceeds the 150-line cap ({self.line_count} lines). Split now."
})
elif self.line_count > 120:
issues.append({
"type": "length_warning",
"severity": "medium",
"message": f"File exceeds recommended 300 lines ({self.line_count} lines). Consider splitting."
"message": f"File is approaching the 150-line cap ({self.line_count} lines)."
})
# Check if file is too short
@@ -238,12 +244,13 @@ class CLAUDEMDAnalyzer:
"""
score = 0
# Length appropriateness (25 points)
if 50 <= self.line_count <= 300:
# Length appropriateness (25 points). Hard cap is 150 lines; anything
# above that loses points sharply because it indicates context bloat.
if 50 <= self.line_count <= 150:
score += 25
elif 30 <= self.line_count < 50 or 300 < self.line_count <= 400:
elif 30 <= self.line_count < 50 or 150 < self.line_count <= 200:
score += 15
elif self.line_count > 400:
elif self.line_count > 200:
score += 5
else:
score += 10
@@ -329,9 +336,9 @@ class CLAUDEMDAnalyzer:
recommendations.append(f"CRITICAL: {issue['message']}")
# Length recommendations
if self.line_count > 300:
if self.line_count > 150:
recommendations.append(
"Reduce root CLAUDE.md to <150 lines - move detailed guides to context-specific files"
"Reduce this CLAUDE.md to <=150 lines (hard cap) - move detail to context-specific files and chain them via @path imports"
)
elif self.line_count < 30:
recommendations.append(
@@ -0,0 +1,54 @@
---
name: claude-md-dependency-rescan
description: Re-detect this project's tech stack from package.json / requirements.txt / pyproject.toml / go.mod / Cargo.toml and diff it against the Tech Stack section of every CLAUDE.md. Read-only — returns added / removed / renamed dependencies, never edits.
when_to_use: |
Use when the user asks "is my Tech Stack section up to date?", "what deps changed?",
"rescan my dependencies", after dependency upgrades, or as part of /sync-claude-md --weekly.
argument-hint: "[manifest-path]"
context: fork
agent: Explore
allowed-tools:
- Read
- Glob
- Grep
- "Bash(find:*)"
- "Bash(cat:*)"
disable-model-invocation: false
---
# CLAUDE.md Dependency Rescan (forked, read-only)
Optional explicit manifest: `$ARGUMENTS` (default: auto-detect all five manifest types).
Run these steps in order. Do not modify any file.
1. **Detect manifests.** Look for `package.json`, `requirements.txt`, `pyproject.toml`, `go.mod`, `Cargo.toml` at the repo root and one level deep (workspaces/monorepos).
2. **Extract declared dependencies** from each:
- `package.json` → keys of `dependencies` and `devDependencies` (skip versions).
- `requirements.txt` → first token of each non-comment line.
- `pyproject.toml``[project.dependencies]` / `[tool.poetry.dependencies]` keys.
- `go.mod` → module paths under `require (...)`.
- `Cargo.toml` → keys under `[dependencies]` / `[dev-dependencies]`.
3. **Inventory documented deps** in every `CLAUDE.md` (and `.claude/rules/*.md`): grep for the Tech Stack / Dependencies sections and the lists under them.
4. **Compute three sets per file:**
- `added`: in manifest but absent from this CLAUDE.md.
- `removed`: documented in this CLAUDE.md but absent from manifest.
- `renamed`: documented and present in manifest but spelled differently (`react-router` vs `react-router-dom`, `pg` vs `psycopg2`).
5. **Return** in this exact shape:
```
## Dependency Rescan
Manifests detected: <list>
Total declared deps: <count>
### Per file
#### <path-to-CLAUDE.md>
- Added (in manifest, not documented): <list or "none">
- Removed (documented, not in manifest): <list or "none">
- Renamed / aliased: <list or "none">
```
6. If every documented set matches its manifest, return exactly `## Dependency Rescan\n\nAll documented deps match manifests. <M> files inspected.`. Do not pad.
**Hard rule**: do not propose specific edits — just surface the diffs. `/sync-claude-md` decides whether to write them.
+51
View File
@@ -0,0 +1,51 @@
---
name: claude-md-drift-audit
description: Audit every CLAUDE.md in this project for drift against the last week of git history. Flags sections that reference deleted files, renamed paths, or removed dependencies. Read-only — returns a punch list, never edits.
when_to_use: |
Use when the user asks "is my CLAUDE.md still accurate?", "audit my docs for staleness",
"what changed in the last week?", or as part of /sync-claude-md --weekly.
argument-hint: "[days=7]"
context: fork
agent: Explore
allowed-tools:
- Read
- Glob
- Grep
- "Bash(git log:*)"
- "Bash(git diff:*)"
- "Bash(git status:*)"
- "Bash(find:*)"
disable-model-invocation: false
---
# CLAUDE.md Drift Audit (forked, read-only)
Days window: `$ARGUMENTS` (default `7` if empty).
Perform these steps in order, then return a single punch-list summary. Do not modify any file.
1. **Inventory** every `CLAUDE.md` and `*.claude/rules/*.md` in the tree using `find . -name "CLAUDE.md" -type f -not -path "*/.git/*" -not -path "*/node_modules/*"`. List paths and line counts.
2. **Collect change signal** for the window:
- `git log --since="$ARGUMENTS days ago" --name-status --no-merges --diff-filter=DR` → deleted and renamed paths.
- `git diff "@{$ARGUMENTS days ago}" --name-status -- package.json requirements.txt pyproject.toml go.mod Cargo.toml 2>/dev/null` → manifest deltas (removed/added deps).
3. **Cross-reference** each CLAUDE.md against those signals using `grep` / `Read`:
- Mark any line that names a deleted or renamed path.
- Mark any line in a Tech Stack / Dependencies section that names a removed dep.
- Mark any `@path/...` chain import or markdown link whose target was deleted.
4. **Return** the punch list in this exact shape (markdown), nothing else:
```
## Drift Audit (window: <N> days)
Total CLAUDE.md inspected: <count>
Signals examined: <deleted_paths>, <renamed_paths>, <removed_deps>
### Findings
- <path>:<line> — <one-sentence reason> — suggested action
- ... (one bullet per drift; omit section if empty)
### Clean
- <path> (lines unchanged, references valid)
```
5. If no drift is found, return exactly `## Drift Audit\n\nNo drift in <N>-day window. <count> files inspected.`. Do not pad the output.
+51
View File
@@ -0,0 +1,51 @@
---
name: claude-md-link-check
description: Verify every @path chain import and every markdown link inside every CLAUDE.md in this project resolves to an existing file. Read-only — returns broken links with file:line refs, never edits.
when_to_use: |
Use when the user asks "check my CLAUDE.md links", "are the @-imports still valid?",
"find broken cross-references", or as part of /sync-claude-md --weekly.
argument-hint: "[path-glob]"
context: fork
agent: Explore
allowed-tools:
- Read
- Glob
- Grep
- "Bash(find:*)"
- "Bash(test:*)"
- "Bash(ls:*)"
disable-model-invocation: false
---
# CLAUDE.md Link Check (forked, read-only)
Optional path-glob: `$ARGUMENTS` (default `.` — entire tree).
Run these steps in order. Do not modify any file.
1. **Inventory.** `find <root> -name "CLAUDE.md" -type f -not -path "*/.git/*" -not -path "*/node_modules/*"`. Also include `.claude/rules/*.md`. Record paths.
2. **Extract candidates** from each file:
- **Chain imports** — lines matching `^@\S+`. The literal after `@` is a relative path.
- **Markdown links** — `[text](target)` where `target` is not an HTTP(S) URL, not `mailto:`, not a bare anchor `#section`.
3. **Resolve each candidate** relative to the file containing it (use `Read` on the parent file to confirm position, then `test -e <resolved-path>` or `Glob`).
- For `@../CLAUDE.md` inside `skill/CLAUDE.md`, resolved path is `CLAUDE.md`.
- For `[Backend](backend/CLAUDE.md)` inside the root, resolved path is `backend/CLAUDE.md`.
4. **Return** the report in this exact shape:
```
## Link Check
Files inspected: <count>
References checked: <chain_imports> @-imports, <md_links> markdown links
### Broken
- <file>:<line> — `<original-target>` → does not resolve (expected `<absolute-path>`)
- ... (omit section if empty)
### Clean
<count> references resolved.
```
5. If everything resolves, return exactly `## Link Check\n\nAll <N> references resolved across <M> files.`. Do not pad.
**Hard rule**: never invent a fix. Report the broken target verbatim. Repair is the user's call (or `/sync-claude-md`'s).
+114 -15
View File
@@ -27,16 +27,48 @@ class ContentGenerator:
"""
Generate root CLAUDE.md file (navigation hub).
When the surrounding project already contains ``AGENTS.md``,
``.cursorrules``, or ``.windsurfrules`` (passed via
``project_context['existing_instruction_files']``), the generated root
file prepends ``@`` imports for them so Claude inherits their content
instead of duplicating it.
Returns:
Complete CLAUDE.md content as string
Complete CLAUDE.md content as string.
"""
template = self.template_selector.select_template()
# Use template selector's customization
if template.get('modular_recommended'):
return self._generate_modular_root(template)
body = self._generate_modular_root(template)
else:
return self._generate_standalone_file(template)
body = self._generate_standalone_file(template)
return self._prepend_existing_instruction_imports(body)
def _prepend_existing_instruction_imports(self, body: str) -> str:
"""Insert ``@`` import lines for any AGENTS.md / cursor / windsurf files."""
existing = self.project_context.get('existing_instruction_files') or []
supported = ('AGENTS.md', '.cursorrules', '.windsurfrules')
imports = [name for name in supported if name in existing]
if not imports:
return body
# Insert right after the intro paragraph (first blank line after the H1).
lines = body.split('\n')
out: List[str] = []
inserted = False
for i, line in enumerate(lines):
out.append(line)
if not inserted and i > 0 and line == '' and lines[i - 1].strip():
out.append('## External Instructions')
out.append('')
out.append('Chained from sibling instruction files in this repo:')
out.append('')
for name in imports:
out.append(f"@{name}")
out.append('')
inserted = True
return '\n'.join(out) if inserted else body
def _generate_modular_root(self, template: Dict[str, Any]) -> str:
"""Generate root file for modular architecture (navigation hub)."""
@@ -99,7 +131,8 @@ class ContentGenerator:
context: Context name ('backend', 'frontend', 'database', etc.)
Returns:
Context-specific CLAUDE.md content
Context-specific CLAUDE.md content with a back-link to the root
CLAUDE.md so Claude can navigate back up the chain.
"""
generators = {
'backend': self._generate_backend_file,
@@ -110,7 +143,59 @@ class ContentGenerator:
}
generator = generators.get(context, self._generate_generic_context_file)
return generator()
body = generator()
# Depth-aware back-link: most context dirs are one level deep, but
# ``.github`` may sit at the repo root next to the root CLAUDE.md.
rel_root = "../CLAUDE.md"
backlink = (
"> Parent context: see the root [CLAUDE.md]"
f"({rel_root}) for project-wide guidelines and behavioural rules.\n"
f"> Chained import: `@{rel_root}`\n\n"
)
return backlink + body
def generate_rules_file(
self,
name: str,
description: str,
paths: List[str],
body: str,
) -> str:
"""Emit a path-scoped ``.claude/rules/*.md`` instruction file.
Anthropic loads these conditionally when Claude accesses files matching
any of the ``paths:`` globs. Use this for sections that would otherwise
bloat the root CLAUDE.md — e.g. backend-only standards that only matter
when editing files under ``src/backend/**``.
Args:
name: Skill-style identifier, kebab-case (e.g. ``backend-rules``).
description: Single-sentence summary (used by Claude for matching).
paths: List of glob patterns that trigger the load.
body: Markdown content (excluding frontmatter and title).
Returns:
Complete file content ready to write to ``.claude/rules/<name>.md``.
Stays within the 150-line cap unless the caller passes a body that
already exceeds it; the cap is enforced by the validator hook.
"""
if not paths:
raise ValueError("paths must be a non-empty list of glob patterns")
lines: List[str] = ["---"]
lines.append(f"name: {name}")
lines.append(f"description: {description}")
lines.append("paths:")
for glob in paths:
lines.append(f" - {glob}")
lines.append("---")
lines.append("")
lines.append(f"# {name.replace('-', ' ').title()}")
lines.append("")
lines.append(body.strip())
lines.append("")
return "\n".join(lines)
def _generate_backend_file(self) -> str:
"""Generate backend-specific CLAUDE.md."""
@@ -412,22 +497,36 @@ class ContentGenerator:
return sections
def _generate_navigation_section(self, template: Dict[str, Any]) -> List[str]:
"""Generate navigation section for modular architecture."""
"""Generate navigation section for modular architecture.
Emits both human-readable markdown links and Claude Code ``@`` imports
so the chained CLAUDE.md files are loaded automatically when the root
file is read.
"""
project_type = self.project_context.get('type')
links = []
targets: List[tuple] = [] # (label, relative_path)
if project_type == 'fullstack':
links.append("- [Backend Guidelines](backend/CLAUDE.md)")
links.append("- [Frontend Guidelines](frontend/CLAUDE.md)")
links.append("- [Database Operations](database/CLAUDE.md)")
targets.append(("Backend Guidelines", "backend/CLAUDE.md"))
targets.append(("Frontend Guidelines", "frontend/CLAUDE.md"))
targets.append(("Database Operations", "database/CLAUDE.md"))
if 'cicd' in self.project_context.get('workflows', []):
links.append("- [CI/CD Workflows](.github/CLAUDE.md)")
targets.append(("CI/CD Workflows", ".github/CLAUDE.md"))
if not links:
links.append("- [Add links to context-specific CLAUDE.md files]")
if not targets:
return ["- [Add links to context-specific CLAUDE.md files]"]
return links
lines: List[str] = []
for label, path in targets:
lines.append(f"- [{label}]({path})")
lines.append("")
lines.append("Chained context (Claude Code auto-imports these):")
lines.append("")
for _, path in targets:
lines.append(f"@{path}")
return lines
def _generate_core_principles(self, template: Dict[str, Any], max_count: int = 7) -> List[str]:
"""Generate core principles list."""
+28
View File
@@ -2,6 +2,34 @@
name: karpathy-guidelines
description: Behavioral guardrails for LLM-assisted coding. Use when writing, reviewing, or refactoring code in any project to avoid overcomplication, keep changes surgical, surface assumptions early, and execute against verifiable success criteria.
license: MIT
paths:
- "**/*.py"
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
- "**/*.go"
- "**/*.rs"
- "**/*.java"
- "**/*.kt"
- "**/*.rb"
- "**/*.php"
- "**/*.swift"
- "**/*.c"
- "**/*.cc"
- "**/*.cpp"
- "**/*.h"
- "**/*.hpp"
- "**/*.cs"
- "**/*.scala"
- "**/*.sh"
- "**/*.bash"
- "**/*.zsh"
- "**/*.sql"
allowed-tools:
- Read
- Glob
- Grep
permissions:
allow:
- Read
+8 -4
View File
@@ -78,7 +78,11 @@ class TemplateSelector:
}
}
# Team size templates
# Team size templates. All targets stay <= MAX_LINES_PER_FILE (150) so any
# single CLAUDE.md remains within the hard cap; larger projects spread
# content across modular sub-files instead of growing the root file.
MAX_LINES_PER_FILE = 150
TEAM_SIZE_TEMPLATES = {
"solo": {
"target_lines": 75,
@@ -87,19 +91,19 @@ class TemplateSelector:
"detail_level": "concise"
},
"small": {
"target_lines": 125,
"target_lines": 100,
"complexity": "core",
"focus": "Core guidelines, collaboration basics",
"detail_level": "moderate"
},
"medium": {
"target_lines": 200,
"target_lines": 125,
"complexity": "detailed",
"focus": "Team coordination, process standardization",
"detail_level": "comprehensive"
},
"large": {
"target_lines": 275,
"target_lines": 150,
"complexity": "comprehensive",
"focus": "Enterprise standards, governance",
"detail_level": "extensive"
+30 -4
View File
@@ -12,9 +12,10 @@ import re
class BestPracticesValidator:
"""Validates CLAUDE.md files against best practices and guidelines."""
# Maximum recommended line count
MAX_RECOMMENDED_LINES = 300
WARNING_THRESHOLD_LINES = 200
# Hard cap: every CLAUDE.md (root or modular) must stay under this.
# Modular split is required when content would exceed this cap.
MAX_RECOMMENDED_LINES = 150
WARNING_THRESHOLD_LINES = 120
# Minimum content requirements
MIN_LINES = 20
@@ -65,18 +66,24 @@ class BestPracticesValidator:
}
]
def __init__(self, content: str, project_context: Dict[str, Any] = None):
def __init__(self, content: str, project_context: Dict[str, Any] = None, filename: str = None):
"""
Initialize validator with CLAUDE.md content.
Args:
content: Full text content of CLAUDE.md file
project_context: Optional project context for advanced validation
filename: Optional path or basename. When the basename ends with
``.local.md`` (e.g. ``CLAUDE.local.md``), the 150-line cap is
relaxed because the file is a personal/gitignored override
outside the chained team-shared tree.
"""
self.content = content
self.lines = content.split('\n')
self.line_count = len(self.lines)
self.project_context = project_context or {}
self.filename = filename or ""
self.is_local_override = self.filename.endswith('.local.md')
def validate_all(self) -> Dict[str, Any]:
"""
@@ -111,6 +118,25 @@ class BestPracticesValidator:
message = f"File length is appropriate ({self.line_count} lines)"
severity = "info"
# CLAUDE.local.md (and any *.local.md sibling) is a personal,
# gitignored override outside the chained team-shared tree. Skip the
# 150-line cap — only flag underuse.
if self.is_local_override:
if self.line_count < self.MIN_LINES:
status = "fail"
message = f"Personal override is too short ({self.line_count} lines, minimum {self.MIN_LINES})"
severity = "low"
else:
message = f"Personal override ({self.line_count} lines, cap waived)"
return {
"check": "file_length",
"status": status,
"message": message,
"severity": severity,
"actual_value": self.line_count,
"expected_range": f"{self.MIN_LINES}+ lines (cap waived for *.local.md)",
}
if self.line_count > self.MAX_RECOMMENDED_LINES:
status = "fail"
message = f"File exceeds maximum recommended length ({self.line_count} > {self.MAX_RECOMMENDED_LINES} lines)"