mirror of
https://github.com/alirezarezvani/ClaudeForge.git
synced 2026-07-03 02:13:15 -04:00
feat(plugin): command metadata, scoped skills, local-tier support, layered hooks, Stop audit
Wave 3 - adoption hardening. Patterns adapted (in original prose, with
attribution) from MIT-licensed shanraisshan/claude-code-best-practice.
Commands (command/enhance-claude-md.md, command/sync-claude-md.md):
- Add allowed-tools / disallowedTools / argument-hint / when_to_use so the
commands auto-suggest in the slash menu and avoid permission prompts.
- disallowedTools blocks WebFetch + WebSearch on both commands.
- Drop the previous broken hooks block (array-of-{matcher, commands} shape
did not match canonical schema; was never firing).
Skills:
- skill/karpathy-guidelines/SKILL.md: paths: glob over 23 code-file
extensions, so the guardrails auto-load only when editing source, not
markdown or data.
- skill/SKILL.md: model: haiku, effort: medium, paths: scoped to CLAUDE.md
+ AGENTS.md + .claude/rules/*.md so validator/generator passes run
cheaply without changing the user-facing model.
CLAUDE.local.md personal tier:
- skill/validator.py BestPracticesValidator now accepts filename=; any
*.local.md basename waives the 150-line cap.
- hooks/validate-claude-md.py reads the exempt suffix from hooks-config.
- .gitignore covers CLAUDE.local.md, **/CLAUDE.local.md,
.claude/settings.local.json, hooks/hooks-config.local.json.
Layered hook config:
- hooks/hooks-config.json: committed defaults
(validateClaudeMd.enabled/maxLines/exemptFilenameSuffix/exitCodeOnViolation,
stopAuditLine.enabled).
- hooks/validate-claude-md.py merges hooks-config.json +
hooks-config.local.json key-by-key; honours enabled=false (silent
exit 0), configurable cap, configurable exit code.
Stop audit hook:
- hooks/audit-claude-md.py walks the project tree, prints one stderr
line: total tracked / OVER cap / near cap (>=80%). Respects
stopAuditLine.enabled from config.
- hooks/hooks.json registers Stop event with matcher "".
Guardian fail-closed contract:
- agent/claude-md-guardian.md Safety & Validation section now explicitly
requires Skill-tool invocation (no inline paraphrase of SKILL.md),
abort on missing validated output, never auto-commit, and respect
local hook config.
Verified (8/8 smoke tests):
- Both commands parse with new fields and no broken hooks block.
- karpathy paths: 23 globs, includes .py/.ts/.go/.rs.
- skill model=haiku effort=medium with CLAUDE.md path scope.
- Validator: *.local.md (300 lines) -> pass; CLAUDE.md (300) -> fail;
legacy ctor without filename -> default behavior preserved.
- hooks-config.json valid; validateClaudeMd.enabled=true, maxLines=150.
- Hook validator: default rc=2 on bloated, rc=0 when local override
disables it, rc=0 on *.local.md (exempt).
- Stop hook entry present; audit script: rc=0 with "5 CLAUDE.md tracked".
- Regression: large-fullstack root still 52 lines with chain imports.
This commit is contained in:
@@ -85,3 +85,11 @@ subagent-claude-md-guardian/
|
|||||||
|
|
||||||
# Optional project files
|
# Optional project files
|
||||||
PROJECT_SUMMARY.md
|
PROJECT_SUMMARY.md
|
||||||
|
|
||||||
|
# ClaudeForge: personal / machine-local overrides outside the chained tree
|
||||||
|
CLAUDE.local.md
|
||||||
|
**/CLAUDE.local.md
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# ClaudeForge: per-developer hook overrides (e.g. disable validator locally)
|
||||||
|
hooks/hooks-config.local.json
|
||||||
|
|||||||
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added (wave 3 — adoption hardening)
|
||||||
|
|
||||||
|
- **Command discovery metadata** (`command/enhance-claude-md.md`, `command/sync-claude-md.md`): both commands now declare `allowed-tools`, `disallowedTools` (blocks `WebFetch`/`WebSearch`), `argument-hint`, and `when_to_use` so Claude Code can auto-suggest and zero-prompt them.
|
||||||
|
- **Path-scoped Karpathy guidelines** (`skill/karpathy-guidelines/SKILL.md`): `paths:` glob on code-file extensions (`*.py`, `*.ts`, `*.go`, `*.rs`, etc.) so the guardrails load only when editing code, not when editing markdown or data.
|
||||||
|
- **Cheaper skill execution** (`skill/SKILL.md`): `model: haiku`, `effort: medium`, and `paths:` scoping the skill to CLAUDE.md / AGENTS.md / `.claude/rules/*.md` so validator + generator passes run cheaply without affecting the user-facing model.
|
||||||
|
- **`CLAUDE.local.md` personal tier**: `validator.BestPracticesValidator` now accepts `filename=` and waives the 150-line cap for any `*.local.md` file. `hooks/validate-claude-md.py` is exempt-suffix aware too. `.gitignore` excludes `CLAUDE.local.md`, `**/CLAUDE.local.md`, `.claude/settings.local.json`, and `hooks/hooks-config.local.json`.
|
||||||
|
- **Layered hook config** (`hooks/hooks-config.json` shared + `hooks/hooks-config.local.json` gitignored): `validate-claude-md.py` merges the two and honours `validateClaudeMd.enabled: false`, `maxLines`, `exemptFilenameSuffix`, and `exitCodeOnViolation`. Teams can opt out per developer without forking the shipped config.
|
||||||
|
- **`Stop` audit hook** (`hooks/audit-claude-md.py` + entry in `hooks/hooks.json`): prints a 1-line summary to stderr at session end — total CLAUDE.md tracked, count over the cap, count near it — so users see drift before the session's context is lost.
|
||||||
|
- **Fail-closed contract on guardian** (`agent/claude-md-guardian.md` Safety & Validation section): the guardian now states it invokes `claude-md-enhancer` exclusively through the Skill tool (never paraphrases SKILL.md content), aborts on missing validated output, never auto-commits, and respects the local hook config.
|
||||||
|
|
||||||
|
Patterns adapted (with attribution and in original prose) from the MIT-licensed [shanraisshan/claude-code-best-practice](https://github.com/shanraisshan/claude-code-best-practice).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **Guardian agent hook frontmatter** (`agent/claude-md-guardian.md`): rewritten from the array-of-objects shape (`hooks: [{ event, commands }]`) to Anthropic's canonical keyed-object shape (`hooks: { EventName: [{ matcher, hooks: [{ type: "command", command }] }] }`). The previous shape did not match the documented schema, so the guardian's hooks did not fire. ([docs](https://code.claude.com/docs/en/hooks))
|
- **Guardian agent hook frontmatter** (`agent/claude-md-guardian.md`): rewritten from the array-of-objects shape (`hooks: [{ event, commands }]`) to Anthropic's canonical keyed-object shape (`hooks: { EventName: [{ matcher, hooks: [{ type: "command", command }] }] }`). The previous shape did not match the documented schema, so the guardian's hooks did not fire. ([docs](https://code.claude.com/docs/en/hooks))
|
||||||
|
|||||||
@@ -241,8 +241,14 @@ The slash command can invoke me:
|
|||||||
|
|
||||||
## Safety & Validation
|
## Safety & Validation
|
||||||
|
|
||||||
**Critical Validation Rule**:
|
**Fail-closed contract** (non-negotiable):
|
||||||
"Always validate your output against official native examples before declaring complete."
|
|
||||||
|
- I invoke `claude-md-enhancer` exclusively through the **Skill tool**. I never read its `SKILL.md` body and act on a paraphrase of it — paraphrase drift is the most common silent-degradation mode for auto-CLAUDE.md tooling.
|
||||||
|
- If the skill returns no validated output (missing required sections, validator status ≠ `pass`, or any thrown exception), I **abort the run** and leave the existing CLAUDE.md tree untouched. Partial writes are worse than stale documentation.
|
||||||
|
- I never commit on my own. Every change lands in the working tree only; the user reviews `git diff` and chooses when to commit.
|
||||||
|
- I respect `hooks/hooks-config.local.json`. If a developer has disabled the validator locally, I treat the cap as advisory for that machine but still warn on the Stop hook.
|
||||||
|
|
||||||
|
**Critical Validation Rule**: validate every emitted file against the reference templates in `skill/examples/` and the canonical schema before declaring success.
|
||||||
|
|
||||||
**My validation checklist**:
|
**My validation checklist**:
|
||||||
- ✅ Project Structure diagram present
|
- ✅ Project Structure diagram present
|
||||||
@@ -250,6 +256,8 @@ The slash command can invoke me:
|
|||||||
- ✅ Architecture section reflects actual patterns
|
- ✅ Architecture section reflects actual patterns
|
||||||
- ✅ Tech Stack lists all major dependencies
|
- ✅ Tech Stack lists all major dependencies
|
||||||
- ✅ Common Commands match package.json scripts
|
- ✅ Common Commands match package.json scripts
|
||||||
|
- ✅ Every emitted CLAUDE.md ≤ 150 lines (cap waived only for `*.local.md`)
|
||||||
|
- ✅ Every sub-CLAUDE.md back-links to root; root has matching `@`-imports
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
---
|
---
|
||||||
description: Initialize or enhance CLAUDE.md files using the claude-md-enhancer skill with interactive workflow and 100% native format compliance
|
description: Initialize or enhance a CLAUDE.md (and chained sub-CLAUDE.md files) for the current project using the claude-md-enhancer skill. Delegates deep codebase scans to the Explore subagent and stays within the 150-line cap.
|
||||||
|
argument-hint: "[--init | --enhance | <path-to-CLAUDE.md>]"
|
||||||
|
when_to_use: |
|
||||||
|
Use whenever a project has no CLAUDE.md, when an existing one is over 150 lines,
|
||||||
|
when an /init result needs to be hardened against context bloat, or when a repo
|
||||||
|
already uses AGENTS.md / .cursorrules / .windsurfrules and you want a Claude-
|
||||||
|
aware root that chains to them via @-imports instead of overwriting.
|
||||||
|
allowed-tools:
|
||||||
|
- Read
|
||||||
|
- Edit
|
||||||
|
- Write
|
||||||
|
- Glob
|
||||||
|
- Grep
|
||||||
|
- Skill
|
||||||
|
- "Bash(ls:*)"
|
||||||
|
- "Bash(find:*)"
|
||||||
|
- "Bash(git status:*)"
|
||||||
|
- "Bash(git diff:*)"
|
||||||
|
- "Bash(wc:*)"
|
||||||
|
disallowedTools:
|
||||||
|
- WebFetch
|
||||||
|
- WebSearch
|
||||||
permissions:
|
permissions:
|
||||||
allow:
|
allow:
|
||||||
- Bash(ls:*)
|
- "Bash(ls:*)"
|
||||||
- Bash(find:*)
|
- "Bash(find:*)"
|
||||||
- Bash(git status:*)
|
- "Bash(git status:*)"
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- Skill
|
- Skill
|
||||||
hooks:
|
|
||||||
- matcher: ""
|
|
||||||
once: true
|
|
||||||
commands:
|
|
||||||
- echo "Starting CLAUDE.md enhancement workflow"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# CLAUDE.md Enhancer Command
|
# CLAUDE.md Enhancer Command
|
||||||
|
|||||||
+32
-14
@@ -1,26 +1,44 @@
|
|||||||
---
|
---
|
||||||
description: Walk every CLAUDE.md in the project, prune stale references, enforce the 150-line cap, and re-chain root ↔ subdirectory files.
|
description: Walk every CLAUDE.md in the project, prune stale references (removed deps, deleted paths, broken modular links), enforce the 150-line cap by splitting into sub-files, and repair the root ↔ subdirectory chain (markdown links + @path imports).
|
||||||
|
argument-hint: "[--dry-run | --paths-only | <directory>]"
|
||||||
|
when_to_use: |
|
||||||
|
Run after refactors, dependency changes, deleted directories, or when any single
|
||||||
|
CLAUDE.md is near the 150-line cap. Also run before cutting a release so the
|
||||||
|
documentation tag-snapshot is truthful.
|
||||||
|
allowed-tools:
|
||||||
|
- Read
|
||||||
|
- Edit
|
||||||
|
- Write
|
||||||
|
- Glob
|
||||||
|
- Grep
|
||||||
|
- Skill
|
||||||
|
- "Bash(ls:*)"
|
||||||
|
- "Bash(find:*)"
|
||||||
|
- "Bash(git status:*)"
|
||||||
|
- "Bash(git diff:*)"
|
||||||
|
- "Bash(wc:*)"
|
||||||
|
- "Bash(grep:*)"
|
||||||
|
- "Bash(cat:*)"
|
||||||
|
- "Bash(test:*)"
|
||||||
|
disallowedTools:
|
||||||
|
- WebFetch
|
||||||
|
- WebSearch
|
||||||
permissions:
|
permissions:
|
||||||
allow:
|
allow:
|
||||||
- Bash(ls:*)
|
- "Bash(ls:*)"
|
||||||
- Bash(find:*)
|
- "Bash(find:*)"
|
||||||
- Bash(git status:*)
|
- "Bash(git status:*)"
|
||||||
- Bash(git diff:*)
|
- "Bash(git diff:*)"
|
||||||
- Bash(wc:*)
|
- "Bash(wc:*)"
|
||||||
- Bash(grep:*)
|
- "Bash(grep:*)"
|
||||||
- Bash(cat:*)
|
- "Bash(cat:*)"
|
||||||
- Bash(test:*)
|
- "Bash(test:*)"
|
||||||
- Read
|
- Read
|
||||||
- Edit
|
- Edit
|
||||||
- Write
|
- Write
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
- Skill
|
- Skill
|
||||||
hooks:
|
|
||||||
- matcher: ""
|
|
||||||
once: true
|
|
||||||
commands:
|
|
||||||
- echo "Starting CLAUDE.md sync workflow"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# /sync-claude-md — CLAUDE.md Sync & Cleanup
|
# /sync-claude-md — CLAUDE.md Sync & Cleanup
|
||||||
|
|||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""ClaudeForge Stop hook: print a 1-line CLAUDE.md health summary.
|
||||||
|
|
||||||
|
Walks the project tree from ``CLAUDE_PROJECT_DIR`` (or the cwd) and prints a
|
||||||
|
single line to stderr summarising how many CLAUDE.md files exist, how close
|
||||||
|
they are to the 150-line cap, and whether any are over. Designed to be the
|
||||||
|
last signal before a session's context is lost — drift visible to the user
|
||||||
|
without forcing them to run ``/sync-claude-md`` blindly.
|
||||||
|
|
||||||
|
Honours ``hooks/hooks-config.json`` and ``hooks/hooks-config.local.json``:
|
||||||
|
when ``stopAuditLine.enabled`` is ``false``, this script exits silently.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
DEFAULT_MAX_LINES = 150
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config() -> dict:
|
||||||
|
cfg: dict = {}
|
||||||
|
for name in ("hooks-config.json", "hooks-config.local.json"):
|
||||||
|
path = os.path.join(HERE, name)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(path, encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
continue
|
||||||
|
cfg.update(data.get("stopAuditLine") or {})
|
||||||
|
cfg.setdefault("maxLines", (data.get("validateClaudeMd") or {}).get("maxLines"))
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def _project_root() -> str:
|
||||||
|
return os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_claude_md(root: str):
|
||||||
|
skip_dirs = {".git", "node_modules", ".venv", "venv", "dist", "build", "vendor"}
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root):
|
||||||
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
||||||
|
for name in filenames:
|
||||||
|
if name == "CLAUDE.md":
|
||||||
|
yield os.path.join(dirpath, name)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
cfg = _load_config()
|
||||||
|
if cfg.get("enabled") is False:
|
||||||
|
return 0
|
||||||
|
cap = int(cfg.get("maxLines") or DEFAULT_MAX_LINES)
|
||||||
|
warn_at = max(1, int(cap * 0.8))
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
over = 0
|
||||||
|
near = 0
|
||||||
|
for path in _iter_claude_md(_project_root()):
|
||||||
|
try:
|
||||||
|
with open(path, encoding="utf-8") as fh:
|
||||||
|
lines = sum(1 for _ in fh)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
total += 1
|
||||||
|
if lines > cap:
|
||||||
|
over += 1
|
||||||
|
elif lines >= warn_at:
|
||||||
|
near += 1
|
||||||
|
|
||||||
|
if total == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
suffix = ""
|
||||||
|
if over:
|
||||||
|
suffix = f" — {over} OVER {cap}-line cap; run /sync-claude-md"
|
||||||
|
elif near:
|
||||||
|
suffix = f" — {near} near cap ({warn_at}+)"
|
||||||
|
print(f"ClaudeForge: {total} CLAUDE.md tracked{suffix}", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$comment": "ClaudeForge shared hook config. Commit this file. Per-developer overrides go in hooks/hooks-config.local.json (gitignored).",
|
||||||
|
"validateClaudeMd": {
|
||||||
|
"enabled": true,
|
||||||
|
"maxLines": 150,
|
||||||
|
"exemptFilenameSuffix": ".local.md",
|
||||||
|
"exitCodeOnViolation": 2
|
||||||
|
},
|
||||||
|
"stopAuditLine": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR}}/hooks/audit-claude-md.py",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,32 @@ import json
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
MAX_LINES = 150
|
DEFAULT_MAX_LINES = 150
|
||||||
|
DEFAULT_EXEMPT_SUFFIX = ".local.md"
|
||||||
|
DEFAULT_VIOLATION_RC = 2
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config() -> dict:
|
||||||
|
"""Merge ``hooks-config.json`` and optional ``hooks-config.local.json``.
|
||||||
|
|
||||||
|
Local file overrides the shared one key-by-key inside ``validateClaudeMd``.
|
||||||
|
Missing files are silently ignored — the script falls back to defaults.
|
||||||
|
"""
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
shared = os.path.join(here, "hooks-config.json")
|
||||||
|
local = os.path.join(here, "hooks-config.local.json")
|
||||||
|
cfg: dict = {}
|
||||||
|
for path in (shared, local):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(path, encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
continue
|
||||||
|
validate_block = data.get("validateClaudeMd") or {}
|
||||||
|
cfg.update(validate_block)
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
def _candidate_paths(payload: dict) -> list[str]:
|
def _candidate_paths(payload: dict) -> list[str]:
|
||||||
@@ -42,9 +67,13 @@ def _candidate_paths(payload: dict) -> list[str]:
|
|||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def _is_claude_md(path: str) -> bool:
|
def _is_claude_md(path: str, exempt_suffix: str) -> bool:
|
||||||
base = os.path.basename(path)
|
base = os.path.basename(path)
|
||||||
return base == "CLAUDE.md" or base.endswith(".claude/rules") or "/.claude/rules/" in path
|
# Personal-tier overrides (CLAUDE.local.md and any matching suffix) are
|
||||||
|
# exempt from the cap — they live outside the chained team-shared tree.
|
||||||
|
if base.endswith(exempt_suffix):
|
||||||
|
return False
|
||||||
|
return base == "CLAUDE.md" or "/.claude/rules/" in path
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
@@ -59,16 +88,23 @@ def main() -> int:
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
cfg = _load_config()
|
||||||
|
if cfg.get("enabled") is False:
|
||||||
|
return 0
|
||||||
|
max_lines = int(cfg.get("maxLines", DEFAULT_MAX_LINES))
|
||||||
|
exempt_suffix = str(cfg.get("exemptFilenameSuffix", DEFAULT_EXEMPT_SUFFIX))
|
||||||
|
violation_rc = int(cfg.get("exitCodeOnViolation", DEFAULT_VIOLATION_RC))
|
||||||
|
|
||||||
violations: list[tuple[str, int]] = []
|
violations: list[tuple[str, int]] = []
|
||||||
for path in _candidate_paths(payload):
|
for path in _candidate_paths(payload):
|
||||||
if not _is_claude_md(path) or not os.path.exists(path):
|
if not _is_claude_md(path, exempt_suffix) or not os.path.exists(path):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open(path, encoding="utf-8") as fh:
|
with open(path, encoding="utf-8") as fh:
|
||||||
line_count = sum(1 for _ in fh)
|
line_count = sum(1 for _ in fh)
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
if line_count > MAX_LINES:
|
if line_count > max_lines:
|
||||||
violations.append((path, line_count))
|
violations.append((path, line_count))
|
||||||
|
|
||||||
if not violations:
|
if not violations:
|
||||||
@@ -76,11 +112,11 @@ def main() -> int:
|
|||||||
|
|
||||||
for path, line_count in violations:
|
for path, line_count in violations:
|
||||||
print(
|
print(
|
||||||
f"ClaudeForge: {path} is {line_count} lines (cap is {MAX_LINES}). "
|
f"ClaudeForge: {path} is {line_count} lines (cap is {max_lines}). "
|
||||||
"Run /sync-claude-md to split into chained sub-files.",
|
"Run /sync-claude-md to split into chained sub-files.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
return 2
|
return violation_rc
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+22
-3
@@ -1,6 +1,25 @@
|
|||||||
---
|
---
|
||||||
name: claude-md-enhancer
|
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.
|
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:
|
permissions:
|
||||||
allow:
|
allow:
|
||||||
- Read
|
- Read
|
||||||
@@ -8,9 +27,9 @@ permissions:
|
|||||||
- Edit
|
- Edit
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
- Bash(ls:*)
|
- "Bash(ls:*)"
|
||||||
- Bash(find:*)
|
- "Bash(find:*)"
|
||||||
- Bash(git:*)
|
- "Bash(git:*)"
|
||||||
---
|
---
|
||||||
|
|
||||||
# CLAUDE.md File Enhancer
|
# CLAUDE.md File Enhancer
|
||||||
|
|||||||
@@ -2,6 +2,34 @@
|
|||||||
name: karpathy-guidelines
|
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.
|
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
|
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:
|
permissions:
|
||||||
allow:
|
allow:
|
||||||
- Read
|
- Read
|
||||||
|
|||||||
+26
-1
@@ -66,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.
|
Initialize validator with CLAUDE.md content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
content: Full text content of CLAUDE.md file
|
content: Full text content of CLAUDE.md file
|
||||||
project_context: Optional project context for advanced validation
|
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.content = content
|
||||||
self.lines = content.split('\n')
|
self.lines = content.split('\n')
|
||||||
self.line_count = len(self.lines)
|
self.line_count = len(self.lines)
|
||||||
self.project_context = project_context or {}
|
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]:
|
def validate_all(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -112,6 +118,25 @@ class BestPracticesValidator:
|
|||||||
message = f"File length is appropriate ({self.line_count} lines)"
|
message = f"File length is appropriate ({self.line_count} lines)"
|
||||||
severity = "info"
|
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:
|
if self.line_count > self.MAX_RECOMMENDED_LINES:
|
||||||
status = "fail"
|
status = "fail"
|
||||||
message = f"File exceeds maximum recommended length ({self.line_count} > {self.MAX_RECOMMENDED_LINES} lines)"
|
message = f"File exceeds maximum recommended length ({self.line_count} > {self.MAX_RECOMMENDED_LINES} lines)"
|
||||||
|
|||||||
Reference in New Issue
Block a user