Skip to content

Commit 06b7e58

Browse files
rootcursoragent
andcommitted
docs: complete stale-pattern checker docs and harden resolver
Align patternsDir fallback with index-sync, guard missing context/, add reporter remediation, update README/CHANGELOG/CONTRIBUTING, and add a test showing the index-sync vs stale-pattern distinction. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1972270 commit 06b7e58

6 files changed

Lines changed: 31 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file.
55
## [Unreleased]
66

77
### Added
8+
- **stale-pattern drift checker** — flags pattern files not linked from `ROUTER.md` or `context/*.md`.
89
- **broken-link drift checker** — flags Markdown links in scaffold files whose local target file does not exist.
910

1011
### Changed
11-
- README and CONTRIBUTING now list all 11 drift checkers (including `tool-config-sync`, `todo-fixme`, and `broken-link`).
12+
- README and CONTRIBUTING now list all 12 drift checkers (including `tool-config-sync`, `todo-fixme`, `broken-link`, and `stale-pattern`).
1213

1314
## [0.3.5] - 2026-05-14
1415

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Thanks for your interest in contributing! Here's how to get started.
44

5-
**New here?** The best starting point is an issue labeled [`good first issue`](https://github.com/theDakshJaitly/mex/labels/good%20first%20issue) — most are self-contained drift checkers, and there are 11 existing checkers to copy from. See [Adding a drift checker](#adding-a-drift-checker) below.
5+
**New here?** The best starting point is an issue labeled [`good first issue`](https://github.com/theDakshJaitly/mex/labels/good%20first%20issue) — most are self-contained drift checkers, and there are 12 existing checkers to copy from. See [Adding a drift checker](#adding-a-drift-checker) below.
66

77
## Setup
88

@@ -50,7 +50,7 @@ test/ # Vitest tests
5050

5151
## Adding a drift checker
5252

53-
New checkers are the most newcomer-friendly contribution. A checker is a small function that inspects scaffold files (or extracted claims) and returns `DriftIssue[]`. There are 11 existing checkers in `src/drift/checkers/` — pick the closest as a template.
53+
New checkers are the most newcomer-friendly contribution. A checker is a small function that inspects scaffold files (or extracted claims) and returns `DriftIssue[]`. There are 12 existing checkers in `src/drift/checkers/` — pick the closest as a template.
5454

5555
1. **Create `src/drift/checkers/<name>.ts`.** There are two shapes:
5656
- **Claim-based** — operates on extracted claims, e.g. `checkPaths(claims, projectRoot, scaffoldRoot)` in `path.ts`.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Editable source: [docs/diagrams/context-routing.excalidraw](docs/diagrams/contex
9696

9797
## Drift Detection
9898

99-
Eleven checkers validate your scaffold against the real codebase. Zero tokens, zero AI.
99+
Twelve checkers validate your scaffold against the real codebase. Zero tokens, zero AI.
100100

101101
| Checker | What it catches |
102102
|---------|----------------|
@@ -111,6 +111,7 @@ Eleven checkers validate your scaffold against the real codebase. Zero tokens, z
111111
| **tool-config-sync** | Installed AI tool config files (e.g. `CLAUDE.md`, `.cursorrules`) out of sync with each other |
112112
| **todo-fixme** | Unresolved `TODO` / `FIXME` markers left in scaffold markdown |
113113
| **broken-link** | Markdown links to local files that do not exist on disk |
114+
| **stale-pattern** | Pattern files not linked from `ROUTER.md` or `context/*.md` |
114115

115116
Scoring starts at 100. mex deducts 10 per error, 3 per warning, and 1 per info.
116117

src/drift/checkers/stale-pattern.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export function checkStalePatterns(
1111
projectRoot: string,
1212
scaffoldRoot: string,
1313
): DriftIssue[] {
14-
const patternsDir = resolve(scaffoldRoot, "patterns");
14+
// Try scaffold root first (deployed as .mex/), then project root
15+
let patternsDir = resolve(scaffoldRoot, "patterns");
16+
if (!existsSync(patternsDir)) {
17+
patternsDir = resolve(projectRoot, "patterns");
18+
}
1519
if (!existsSync(patternsDir)) return [];
1620

1721
const patternFiles = globSync("*.md", { cwd: patternsDir }).filter(
@@ -20,12 +24,11 @@ export function checkStalePatterns(
2024
if (patternFiles.length === 0) return [];
2125

2226
const referenced = new Set<string>();
23-
const sources = [
24-
resolve(scaffoldRoot, "ROUTER.md"),
25-
...globSync("*.md", { cwd: resolve(scaffoldRoot, "context") }).map((f) =>
26-
resolve(scaffoldRoot, "context", f),
27-
),
28-
];
27+
const contextDir = resolve(scaffoldRoot, "context");
28+
const contextSources = existsSync(contextDir)
29+
? globSync("*.md", { cwd: contextDir }).map((f) => resolve(contextDir, f))
30+
: [];
31+
const sources = [resolve(scaffoldRoot, "ROUTER.md"), ...contextSources];
2932

3033
for (const filePath of sources) {
3134
if (!existsSync(filePath)) continue;

src/reporter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ function remediationFor(code: DriftIssue["code"]): string | null {
137137
return "Resolve the TODO/FIXME or remove the marker from the scaffold.";
138138
case "BROKEN_LINK":
139139
return "Fix the link target path or remove the broken Markdown link.";
140+
case "STALE_PATTERN":
141+
return "Link the pattern from ROUTER.md or a context/*.md file so agents can discover it.";
140142
default:
141143
return null;
142144
}

test/checkers.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,17 @@ describe("checkStalePatterns", () => {
570570
const issues = checkStalePatterns(tmpDir, tmpDir);
571571
expect(issues).toHaveLength(0);
572572
});
573+
574+
it("still flags patterns only listed in INDEX.md (index-sync covers INDEX ↔ files)", () => {
575+
mkdirSync(join(tmpDir, "patterns"), { recursive: true });
576+
writeFileSync(join(tmpDir, "ROUTER.md"), "# Router\n");
577+
writeFileSync(
578+
join(tmpDir, "patterns/INDEX.md"),
579+
"| [indexed-only.md](indexed-only.md) | Indexed |\n",
580+
);
581+
writeFileSync(join(tmpDir, "patterns/indexed-only.md"), "# Indexed only\n");
582+
const issues = checkStalePatterns(tmpDir, tmpDir);
583+
expect(issues).toHaveLength(1);
584+
expect(issues[0].file).toBe("patterns/indexed-only.md");
585+
});
573586
});

0 commit comments

Comments
 (0)