@yawlabs/ctxlint 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -24
- package/dist/chunk-WEYNMCAH.js +1562 -0
- package/dist/cli-VYWAONGX.js +354 -0
- package/dist/index.js +5 -1275
- package/dist/mcp/server.js +669 -123
- package/dist/server-7C2IQ7VV.js +202 -0
- package/package.json +11 -3
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALL_CHECKS,
|
|
4
|
+
VERSION,
|
|
5
|
+
applyFixes,
|
|
6
|
+
fileExists,
|
|
7
|
+
findRenames,
|
|
8
|
+
freeEncoder,
|
|
9
|
+
isDirectory,
|
|
10
|
+
parseContextFile,
|
|
11
|
+
resetGit,
|
|
12
|
+
runAudit,
|
|
13
|
+
scanForContextFiles
|
|
14
|
+
} from "./chunk-WEYNMCAH.js";
|
|
15
|
+
|
|
16
|
+
// src/mcp/server.ts
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
var checkEnum = z.enum([
|
|
22
|
+
"paths",
|
|
23
|
+
"commands",
|
|
24
|
+
"staleness",
|
|
25
|
+
"tokens",
|
|
26
|
+
"redundancy",
|
|
27
|
+
"contradictions",
|
|
28
|
+
"frontmatter"
|
|
29
|
+
]);
|
|
30
|
+
var server = new McpServer({
|
|
31
|
+
name: "ctxlint",
|
|
32
|
+
version: VERSION
|
|
33
|
+
});
|
|
34
|
+
server.tool(
|
|
35
|
+
"ctxlint_audit",
|
|
36
|
+
"Audit all AI agent context files (CLAUDE.md, AGENTS.md, etc.) in the project for stale references, invalid commands, redundant content, contradictions, frontmatter issues, and token waste.",
|
|
37
|
+
{
|
|
38
|
+
projectPath: z.string().optional().describe("Path to the project root. Defaults to current working directory."),
|
|
39
|
+
checks: z.array(checkEnum).optional().describe("Which checks to run. Defaults to all.")
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
readOnlyHint: true,
|
|
43
|
+
destructiveHint: false,
|
|
44
|
+
idempotentHint: true,
|
|
45
|
+
openWorldHint: false
|
|
46
|
+
},
|
|
47
|
+
async ({ projectPath, checks }) => {
|
|
48
|
+
const root = path.resolve(projectPath || process.cwd());
|
|
49
|
+
const activeChecks = checks || ALL_CHECKS;
|
|
50
|
+
try {
|
|
51
|
+
const result = await runAudit(root, activeChecks);
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
57
|
+
isError: true
|
|
58
|
+
};
|
|
59
|
+
} finally {
|
|
60
|
+
freeEncoder();
|
|
61
|
+
resetGit();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
server.tool(
|
|
66
|
+
"ctxlint_validate_path",
|
|
67
|
+
"Check if a file path referenced in a context file actually exists in the project. Returns the file status and suggests corrections if the path is invalid.",
|
|
68
|
+
{
|
|
69
|
+
path: z.string().describe("The file path to validate"),
|
|
70
|
+
projectPath: z.string().optional().describe("Project root. Defaults to cwd.")
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
readOnlyHint: true,
|
|
74
|
+
destructiveHint: false,
|
|
75
|
+
idempotentHint: true,
|
|
76
|
+
openWorldHint: false
|
|
77
|
+
},
|
|
78
|
+
async ({ path: filePath, projectPath }) => {
|
|
79
|
+
try {
|
|
80
|
+
const root = path.resolve(projectPath || process.cwd());
|
|
81
|
+
const resolved = path.resolve(root, filePath);
|
|
82
|
+
const result = {
|
|
83
|
+
path: filePath,
|
|
84
|
+
exists: fileExists(resolved) || isDirectory(resolved)
|
|
85
|
+
};
|
|
86
|
+
if (!result.exists) {
|
|
87
|
+
const rename = await findRenames(root, filePath);
|
|
88
|
+
if (rename) {
|
|
89
|
+
result.renamed = true;
|
|
90
|
+
result.newPath = rename.newPath;
|
|
91
|
+
result.renameCommit = rename.commitHash;
|
|
92
|
+
result.daysAgo = rename.daysAgo;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
100
|
+
isError: true
|
|
101
|
+
};
|
|
102
|
+
} finally {
|
|
103
|
+
resetGit();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
server.tool(
|
|
108
|
+
"ctxlint_token_report",
|
|
109
|
+
"Get a token count breakdown for all context files in the project. Shows per-file and aggregate token usage, plus estimated waste from redundant content.",
|
|
110
|
+
{
|
|
111
|
+
projectPath: z.string().optional().describe("Project root. Defaults to cwd.")
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
readOnlyHint: true,
|
|
115
|
+
destructiveHint: false,
|
|
116
|
+
idempotentHint: true,
|
|
117
|
+
openWorldHint: false
|
|
118
|
+
},
|
|
119
|
+
async ({ projectPath }) => {
|
|
120
|
+
const root = path.resolve(projectPath || process.cwd());
|
|
121
|
+
try {
|
|
122
|
+
const discovered = await scanForContextFiles(root);
|
|
123
|
+
const parsed = discovered.map((f) => parseContextFile(f));
|
|
124
|
+
const files = parsed.map((f) => ({
|
|
125
|
+
path: f.relativePath,
|
|
126
|
+
tokens: f.totalTokens,
|
|
127
|
+
lines: f.totalLines,
|
|
128
|
+
isSymlink: f.isSymlink
|
|
129
|
+
}));
|
|
130
|
+
const totalTokens = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: JSON.stringify(
|
|
136
|
+
{ files, totalTokens, note: "Token counts use GPT-4 cl100k_base tokenizer" },
|
|
137
|
+
null,
|
|
138
|
+
2
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
+
return {
|
|
146
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
147
|
+
isError: true
|
|
148
|
+
};
|
|
149
|
+
} finally {
|
|
150
|
+
freeEncoder();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
server.tool(
|
|
155
|
+
"ctxlint_fix",
|
|
156
|
+
"Run the linter with --fix mode to auto-correct broken file paths in context files using git history and fuzzy matching. Returns a summary of what was fixed.",
|
|
157
|
+
{
|
|
158
|
+
projectPath: z.string().optional().describe("Path to the project root. Defaults to current working directory."),
|
|
159
|
+
checks: z.array(checkEnum).optional().describe("Which checks to run before fixing. Defaults to all.")
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
readOnlyHint: false,
|
|
163
|
+
destructiveHint: false,
|
|
164
|
+
idempotentHint: true,
|
|
165
|
+
openWorldHint: false
|
|
166
|
+
},
|
|
167
|
+
async ({ projectPath, checks }) => {
|
|
168
|
+
const root = path.resolve(projectPath || process.cwd());
|
|
169
|
+
const activeChecks = checks || ALL_CHECKS;
|
|
170
|
+
try {
|
|
171
|
+
const result = await runAudit(root, activeChecks);
|
|
172
|
+
const fixSummary = applyFixes(result);
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify(
|
|
178
|
+
{
|
|
179
|
+
totalFixes: fixSummary.totalFixes,
|
|
180
|
+
filesModified: fixSummary.filesModified,
|
|
181
|
+
remainingIssues: result.summary
|
|
182
|
+
},
|
|
183
|
+
null,
|
|
184
|
+
2
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
};
|
|
189
|
+
} catch (err) {
|
|
190
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
193
|
+
isError: true
|
|
194
|
+
};
|
|
195
|
+
} finally {
|
|
196
|
+
freeEncoder();
|
|
197
|
+
resetGit();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
var transport = new StdioServerTransport();
|
|
202
|
+
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/ctxlint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Lint your AI agent context files (CLAUDE.md, AGENTS.md, etc.) against your actual codebase",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ctxlint": "dist/index.js"
|
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
"agents",
|
|
12
12
|
"context",
|
|
13
13
|
"lint",
|
|
14
|
+
"linter",
|
|
14
15
|
"claude-md",
|
|
15
16
|
"agents-md",
|
|
16
17
|
"ai-coding",
|
|
17
18
|
"context-engineering",
|
|
18
|
-
"mcp"
|
|
19
|
+
"mcp",
|
|
20
|
+
"cursorrules",
|
|
21
|
+
"copilot-instructions",
|
|
22
|
+
"windsurfrules",
|
|
23
|
+
"pre-commit",
|
|
24
|
+
"agentic",
|
|
25
|
+
"codex",
|
|
26
|
+
"sarif"
|
|
19
27
|
],
|
|
20
28
|
"files": [
|
|
21
29
|
"dist",
|
|
@@ -30,7 +38,7 @@
|
|
|
30
38
|
"homepage": "https://github.com/yawlabs/ctxlint",
|
|
31
39
|
"author": "Yaw Labs <contact@yaw.sh>",
|
|
32
40
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
41
|
+
"node": ">=18"
|
|
34
42
|
},
|
|
35
43
|
"dependencies": {
|
|
36
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|