Metadata-Version: 2.4
Name: claude-memory-lint
Version: 0.2.1
Summary: A lint for Claude Code memory directories — catches frontmatter rot, oversized files, and stop-word noise before it pollutes Claude's context.
Project-URL: Homepage, https://github.com/hinanohart/claude-memory-lint
Project-URL: Issues, https://github.com/hinanohart/claude-memory-lint/issues
Author: claude-memory-lint contributors
License: MIT
License-File: LICENSE
Keywords: claude-code,context-pollution,lint,markdown,memory
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest-cov>=4; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# claude-memory-lint

> **Disclaimer:** This is an **independent third-party tool**. It is **not affiliated with, endorsed by, or sponsored by Anthropic**. "Claude" and "Claude Code" are trademarks of Anthropic and are used here nominatively to identify the official CLI/product this tool integrates with.

A static lint for Claude Code memory directories. Catches the
file-quality problems that lead to context pollution **before** they
reach the prompt — stop generic-keyword noise at the source instead of
chasing it at runtime.

```text
cml check  ~/.claude/projects/<id>/memory/   # exits 1 on ERROR
cml fix    ~/.claude/projects/<id>/memory/   # auto-add missing aliases
cml stats  ~/.claude/projects/<id>/memory/   # counts only, no contents
```

## Why

Claude Code memory directories grow quickly. Once frontmatter is
inconsistent and a meaningful fraction of files only carry generic
keywords (`api`, `github`, `claude`, `task`, `agent`, …), runtime
routing degrades into "every common word matches every file": context
gets stuffed with unrelated memory and the model's attention drifts.

Runtime routers can patch the symptom. **The cause is on the write
side**: missing frontmatter, empty `aliases:`, oversized files, stale
copies. `claude-memory-lint` raises those defects to ERROR / WARN /
INFO at write-time so they never reach the auto-load path.

## Privacy posture

This is the privacy-conscious choice in the memory-tooling space:

- **No LLM calls.** Period. Heuristics only.
- **No file body in stdout.** `stats` and `check --format json` print
  filenames, rule IDs, and counts — never the file content.
- **Auto-fix writes a `.bak` next to the original** and never sends
  anything off-machine.
- A separate hook (`claude-memory-router`) handles **runtime** routing
  with the same posture; this lint is its compile-time companion.

## Install

```bash
pip install claude-memory-lint
# or for development:
pip install -e .[dev]
```

Python 3.10+. No required runtime dependencies.

## Rules

| ID | Severity | What it catches |
| --- | --- | --- |
| **R001** | ERROR | frontmatter missing or unterminated |
| **R002** | ERROR | both `aliases:` and `triggers:` are empty / absent |
| **R003** | WARN at 25 KiB / ERROR at 30 KiB | file size threshold (lowered from 50 KiB in v0.1.2) |
| **R004** | WARN | filename stem is not kebab-case ASCII |
| **R005** | INFO | **inbound:** file is not referenced by any other memory file (orphan) |
| **R006** | WARN | stop-word density in `name + description` ≥ 40 % |
| **R007** | INFO | duplicate normalised stem (likely stale copy) |
| **R008** | INFO | mtime older than 180 days |
| **R009** | ERROR | secret literal pattern (GitHub PAT / AWS / Anthropic / OpenAI / Google / Slack / Stripe) — match is never echoed in the lint output |
| **R010** | WARN (opt-in) | **outbound:** dangling markdown link `](*.md)` — target not found in tree or `archive/` (enable via `--dangling-links`). Complementary to R005: R005 catches a file no one points at; R010 catches a link that points at nothing |
| **R011** | WARN (opt-in) | stale backup file `*.bak` / `.backup` / `.orig` / trailing `~` older than 7 days in the active directory (enable via `--stale-backup`) |

Thresholds are tunable in code; runtime config file support is on the
roadmap.

R009 (added in v0.2.0) is the response to a real-world incident where a
GitHub PAT literal sat in a memory file for days under `.gitignore`
"protection" while still being one filesystem-read away from the LLM
context. **The rule deliberately reports only the pattern *type* and
the line — the matched substring is never written to stdout, JSON,
SARIF, or any error path** — because a lint that leaks the secret it
caught is worse than no lint.

## Use as a pre-commit hook

Add to your `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/hinanohart/claude-memory-lint
    rev: v0.2.1
    hooks:
      - id: claude-memory-lint
        args: [check, --rule, R001, --rule, R002]
```

This blocks commits that introduce a memory file without proper
frontmatter or aliases.

## Use in CI

```yaml
- name: lint memory directory
  run: |
    pip install claude-memory-lint
    cml check --format sarif path/to/memory > cml.sarif
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: cml.sarif
```

## Companion: `claude-memory-router`

This lint pairs with **[claude-memory-router](https://github.com/hinanohart/claude-memory-router)**:

- The lint enforces *write-time* quality (aliases, size, freshness).
- The router consumes *that* quality at *read-time* and only injects
  the most relevant memory files into the prompt.

Garbage-in, garbage-out is real for routers as for any other engine.
The lint exists so the router can do its best work.

## Output formats

```text
text   default — human-readable, one finding per line + summary
json   machine-readable; convenient for CI gates
sarif  SARIF 2.1.0 — uploadable to GitHub code scanning
```

## Testing

```bash
pip install -e .[dev]
pytest -v
```

Aim for `45+` passing tests covering parser edge cases (no
frontmatter / unterminated frontmatter / inline lists / multi-line
lists), per-rule positive and negative cases, the corpus rule, and the
CLI front-end including JSON / SARIF reporters.

## License

MIT. See `LICENSE`.
