CLAUDE.md@src · diff
git:20260222.b6c1512 to git:20260727.016b46d
2 added, 150 removed. Audit A to B.
- # Core Package (NPM Linter)
-
- ## Commands
-
- ```bash
- npm run build # Compile TypeScript to dist/
- npm run dev # Watch mode (tsc --watch)
- npm test # Full Jest suite
- npm run test:watch # Jest watch mode
- npm run test:validators # Validator tests with coverage
- npm run test:schemas # Schema tests with coverage
- npm run generate:types # Regenerate rule index and ID types
- npm run check:self # Build then run claudelint on itself
- ```
-
- ## Rule Authoring
-
- Rules live in `src/rules/<category>/` and follow this structure:
-
- ```typescript
- export const rule: Rule = {
- meta: {
- id: 'category-rule-name', // Must match filename
- name: 'Human Readable Name',
- description: 'One-line summary',
- category: 'CategoryName',
- severity: 'error' | 'warning',
- fixable: boolean,
- deprecated: boolean,
- since: '0.2.0', // Required for built-in rules, optional for custom rules
- docs: {
- recommended: boolean,
- summary: 'Short summary for docs',
- details: 'Full explanation for docs page',
- examples: { incorrect: [...], correct: [...] },
- },
- },
- create(context: RuleContext) {
- // Validation logic — call context.report() for violations
- },
- };
- ```
-
- ### Adding a New Rule
-
- 1. Create `src/rules/<category>/<rule-name>.ts` following the pattern above
- 2. Create `tests/rules/<rule-name>.test.ts` with passing/failing cases
- 3. Run `npm run generate:types` to register the rule in `src/rules/index.ts` and `src/rules/rule-ids.ts`
- 4. Run `npm run docs:generate` to create the website documentation page
- 5. Run `npm run check:rule-coverage` to verify docs/tests exist
-
- **Do NOT hand-edit `src/rules/index.ts` or `src/rules/rule-ids.ts`** — these are auto-generated.
-
- ### Shared Utilities for Rule Authors
-
- Rules that parse Markdown/YAML content **must** use shared utilities. Do not hand-roll parsing logic.
-
- | Utility | Purpose | Import from |
- |---|---|---|
- | `extractFrontmatter<T>()` | Parse YAML frontmatter into typed object | `utils/formats/markdown` |
- | `extractBodyContent()` | Get content after frontmatter closing `---` | `utils/formats/markdown` |
- | `stripCodeBlocks()` | Remove fenced + inline code blocks (preserves line count) | `utils/formats/markdown` |
- | `getFrontmatterFieldLine()` | Find line number for a specific frontmatter field | `utils/formats/markdown` |
- | `containsEnvVar()` | Check if string contains `${VAR}` or `$VAR` placeholder | `utils/patterns` |
- | `isValidSemver()` | Validate semver format | `utils/patterns` |
- | `escapeRegExp()` | Escape string for safe use in `new RegExp()` | `utils/patterns` |
- | `isImportPath()` | Distinguish `@import` paths from decorators/emails | `utils/patterns` |
- | `ENV_VAR_PLACEHOLDER_RE` | Regex constant for env var placeholders | `utils/patterns` |
- | `SEMVER_RE` | Regex constant for full semver validation | `utils/patterns` |
- | `HEADING_RE` | Regex constant for markdown headings | `utils/patterns` |
-
- **Anti-patterns (enforced by `npm run check:rule-patterns`):**
-
- - Do not use `lastIndex` with global regex -- use `matchAll()` instead
- - Do not use `exec()` in while loops -- use `matchAll()` instead
- - Do not strip code blocks with `/```[\s\S]*?```/g` -- use `stripCodeBlocks()`
- - Do not split frontmatter with `.split('---')` -- use `extractBodyContent()`
- - Do not import `js-yaml` directly -- use `extractFrontmatter()`
- - Do not use `url.includes('$')` for env var detection -- use `containsEnvVar()` from `utils/patterns`
- - Do not duplicate env var placeholder regex -- use `containsEnvVar()` or `ENV_VAR_PLACEHOLDER_RE`
- - Do not inline `escapeRegExp` -- use `escapeRegExp()` from `utils/patterns`
- - Do not duplicate semver regex -- use `isValidSemver()` or `SEMVER_RE` from `utils/patterns`
-
- Pre-parsed data is also available on `context.frontmatter`, `context.bodyContent`, and `context.contentWithoutCode` (lazy-computed).
-
- ## Schemas
-
- - **Zod schemas**: `src/schemas/` — used at runtime for validation
- - **JSON schemas**: `schemas/` — generated from Zod via `npm run generate:json-schemas`
- - Keep in sync: `npm run check:schema-sync` verifies Zod and JSON schemas match
-
- ## Test Structure
-
- ```text
- tests/
- rules/ # One test file per rule
- validators/ # Validator integration tests
- api/ # Public API tests
- schemas/ # Schema validation tests
- fixtures/ # Test project fixtures
- helpers/ # Shared test utilities
- integration/ # End-to-end CLI tests
- ```
-
- Coverage thresholds: 70% branches, 80% functions/lines/statements.
-
- ## Key Directories
-
- - `src/api/` — public programmatic API (`ClaudeLint` class, formatters). Keep stable.
- - `src/cli/` — CLI commands (commander.js)
- - `src/validators/` — orchestrate rules by component type (skills, hooks, settings, etc.)
- - `src/utils/` — shared utilities (markdown parsing, frontmatter extraction, etc.)
- - `src/types/` — TypeScript type definitions
-
- ## Rule Message Guidelines
-
- Messages in `context.report({ message })` are shown in the default CLI table. They must be concise problem statements.
-
- **Max length**: 100 characters (enforced by `npm run check:message-length`).
-
- **Message content rules** (enforced by `npm run check:message-content`):
-
- - State the problem only. No fix instructions, rationale, or examples.
- - Do not start with imperative verbs (Add, Use, Create, Remove, Consider).
- - Do not include "so that", "to ensure", "which means", "e.g.,", "for example".
- - Do not dump lists of valid values. Put those in the `fix` field or `docs.howToFix`.
-
- **Where content belongs**:
-
- | Content type | Field | Shown in |
- |---|---|---|
- | Problem statement | `message` | Default table output |
- | How to fix | `fix` or `docs.howToFix` | `--explain` mode (Fix:) |
- | Why it matters | `docs.rationale` | `--explain` mode (Why:) |
- | Full explanation | `docs.details` | `claudelint explain <rule>` |
- | Code examples | `docs.examples` | `claudelint explain <rule>` |
-
- **Good**: `File exceeds 40KB limit (50001 bytes)`
-
- **Bad**: `File exceeds 40KB limit (50001 bytes). Split content into smaller files using @imports to keep the file manageable.`
-
- ## Conventions
+ # Shared agent guidance
- - All rule IDs are kebab-case and prefixed by category: `skill-name`, `hook-event-type`
- - Rules delegate to Zod schemas where possible (thin wrapper pattern).
- Thin-wrapper rules (e.g., `output-style-name`, `agent-events`) have empty `validate` functions
- because the schema handles validation. They ARE disableable via config: schema errors with
- matching rule IDs are deduplicated in `mergeSchemaValidationResult()`, the thin wrapper reports
- with the ruleId, and `getResult()` respects `severity="off"`.
- - `context.report()` takes `{ message, line?, column?, fix? }`
- - Fixable rules must provide a `fix` function in the report
+ @AGENTS.md