@yawlabs/ctxlint 0.2.1 → 0.2.2
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 +10 -5
- package/dist/index.js +34 -37
- package/dist/mcp/server.js +102 -22
- package/package.json +10 -16
package/README.md
CHANGED
|
@@ -32,18 +32,23 @@ npx @yawlabs/ctxlint
|
|
|
32
32
|
|------|------|
|
|
33
33
|
| `CLAUDE.md`, `CLAUDE.local.md` | Claude Code |
|
|
34
34
|
| `AGENTS.md` | Multi-agent |
|
|
35
|
-
| `.cursorrules`, `.cursor/rules/*.md` | Cursor |
|
|
36
|
-
| `copilot-instructions.md` | GitHub Copilot |
|
|
35
|
+
| `.cursorrules`, `.cursor/rules/*.md`, `.cursor/rules/*.mdc` | Cursor |
|
|
36
|
+
| `copilot-instructions.md`, `.github/copilot-instructions.md`, `.github/instructions/*.md` | GitHub Copilot |
|
|
37
37
|
| `.windsurfrules`, `.windsurf/rules/*.md` | Windsurf |
|
|
38
38
|
| `GEMINI.md` | Gemini |
|
|
39
39
|
| `JULES.md` | Jules |
|
|
40
40
|
| `.clinerules` | Cline |
|
|
41
|
+
| `CODEX.md` | OpenAI Codex CLI |
|
|
42
|
+
| `.aiderules` | Aider |
|
|
43
|
+
| `.aide/rules/*.md` | Aide / Codestory |
|
|
44
|
+
| `.amazonq/rules/*.md` | Amazon Q Developer |
|
|
45
|
+
| `.goose/instructions.md` | Goose by Block |
|
|
41
46
|
| `CONVENTIONS.md` | General |
|
|
42
47
|
|
|
43
48
|
## Example Output
|
|
44
49
|
|
|
45
50
|
```
|
|
46
|
-
ctxlint v0.1
|
|
51
|
+
ctxlint v0.2.1
|
|
47
52
|
|
|
48
53
|
Scanning /Users/you/my-app...
|
|
49
54
|
|
|
@@ -133,11 +138,11 @@ CLI flags override config file settings.
|
|
|
133
138
|
|
|
134
139
|
## Use as MCP Server
|
|
135
140
|
|
|
136
|
-
ctxlint ships with an MCP server that exposes
|
|
141
|
+
ctxlint ships with an MCP server that exposes four tools (`ctxlint_audit`, `ctxlint_validate_path`, `ctxlint_token_report`, `ctxlint_fix`):
|
|
137
142
|
|
|
138
143
|
```bash
|
|
139
144
|
# Claude Code
|
|
140
|
-
claude mcp add ctxlint -- node node_modules/ctxlint/dist/mcp/server.js
|
|
145
|
+
claude mcp add ctxlint -- node node_modules/@yawlabs/ctxlint/dist/mcp/server.js
|
|
141
146
|
|
|
142
147
|
# Or run from source
|
|
143
148
|
claude mcp add ctxlint -- node /path/to/ctxlint/dist/mcp/server.js
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,14 @@ import { glob } from "glob";
|
|
|
18
18
|
// src/utils/fs.ts
|
|
19
19
|
import * as fs from "fs";
|
|
20
20
|
import * as path from "path";
|
|
21
|
+
function loadPackageJson(projectRoot) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8");
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
21
29
|
function fileExists(filePath) {
|
|
22
30
|
try {
|
|
23
31
|
fs.accessSync(filePath);
|
|
@@ -93,12 +101,18 @@ var CONTEXT_FILE_PATTERNS = [
|
|
|
93
101
|
".cursor/rules/*.mdc",
|
|
94
102
|
"copilot-instructions.md",
|
|
95
103
|
".github/copilot-instructions.md",
|
|
104
|
+
".github/instructions/*.md",
|
|
96
105
|
".windsurfrules",
|
|
97
106
|
".windsurf/rules/*.md",
|
|
98
107
|
"GEMINI.md",
|
|
99
108
|
"JULES.md",
|
|
100
109
|
".clinerules",
|
|
101
|
-
"CONVENTIONS.md"
|
|
110
|
+
"CONVENTIONS.md",
|
|
111
|
+
"CODEX.md",
|
|
112
|
+
".aiderules",
|
|
113
|
+
".aide/rules/*.md",
|
|
114
|
+
".amazonq/rules/*.md",
|
|
115
|
+
".goose/instructions.md"
|
|
102
116
|
];
|
|
103
117
|
var IGNORED_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor"]);
|
|
104
118
|
async function scanForContextFiles(projectRoot) {
|
|
@@ -554,7 +568,7 @@ function findClosestMatch(target, files) {
|
|
|
554
568
|
// src/core/checks/commands.ts
|
|
555
569
|
import * as fs3 from "fs";
|
|
556
570
|
import * as path4 from "path";
|
|
557
|
-
var NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?)\s+(\S+)/;
|
|
571
|
+
var NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
558
572
|
var MAKE_PATTERN = /^make\s+(\S+)/;
|
|
559
573
|
async function checkCommands(file, projectRoot) {
|
|
560
574
|
const issues = [];
|
|
@@ -577,7 +591,7 @@ async function checkCommands(file, projectRoot) {
|
|
|
577
591
|
}
|
|
578
592
|
continue;
|
|
579
593
|
}
|
|
580
|
-
const shorthandMatch = cmd.match(/^(npm|pnpm|yarn)\s+(test|start|build|dev|lint|format)\b/);
|
|
594
|
+
const shorthandMatch = cmd.match(/^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/);
|
|
581
595
|
if (shorthandMatch && pkgJson) {
|
|
582
596
|
const scriptName = shorthandMatch[2];
|
|
583
597
|
if (pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
@@ -634,14 +648,6 @@ async function checkCommands(file, projectRoot) {
|
|
|
634
648
|
}
|
|
635
649
|
return issues;
|
|
636
650
|
}
|
|
637
|
-
function loadPackageJson(projectRoot) {
|
|
638
|
-
try {
|
|
639
|
-
const content = fs3.readFileSync(path4.join(projectRoot, "package.json"), "utf-8");
|
|
640
|
-
return JSON.parse(content);
|
|
641
|
-
} catch {
|
|
642
|
-
return null;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
651
|
function loadMakefile(projectRoot) {
|
|
646
652
|
try {
|
|
647
653
|
return fs3.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
|
|
@@ -764,7 +770,6 @@ function checkAggregateTokens(files) {
|
|
|
764
770
|
}
|
|
765
771
|
|
|
766
772
|
// src/core/checks/redundancy.ts
|
|
767
|
-
import * as fs4 from "fs";
|
|
768
773
|
import * as path6 from "path";
|
|
769
774
|
var PACKAGE_TECH_MAP = {
|
|
770
775
|
react: ["React", "react"],
|
|
@@ -819,7 +824,7 @@ var PACKAGE_TECH_MAP = {
|
|
|
819
824
|
};
|
|
820
825
|
async function checkRedundancy(file, projectRoot) {
|
|
821
826
|
const issues = [];
|
|
822
|
-
const pkgJson =
|
|
827
|
+
const pkgJson = loadPackageJson(projectRoot);
|
|
823
828
|
if (pkgJson) {
|
|
824
829
|
const allDeps = /* @__PURE__ */ new Set([
|
|
825
830
|
...Object.keys(pkgJson.dependencies || {}),
|
|
@@ -914,14 +919,6 @@ function calculateLineOverlap(contentA, contentB) {
|
|
|
914
919
|
}
|
|
915
920
|
return overlap / Math.min(linesA.size, linesB.size);
|
|
916
921
|
}
|
|
917
|
-
function loadPackageJson2(projectRoot) {
|
|
918
|
-
try {
|
|
919
|
-
const content = fs4.readFileSync(path6.join(projectRoot, "package.json"), "utf-8");
|
|
920
|
-
return JSON.parse(content);
|
|
921
|
-
} catch {
|
|
922
|
-
return null;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
922
|
function escapeRegex(str) {
|
|
926
923
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
927
924
|
}
|
|
@@ -1026,7 +1023,7 @@ function formatIssue(issue) {
|
|
|
1026
1023
|
}
|
|
1027
1024
|
|
|
1028
1025
|
// src/core/fixer.ts
|
|
1029
|
-
import * as
|
|
1026
|
+
import * as fs4 from "fs";
|
|
1030
1027
|
import chalk2 from "chalk";
|
|
1031
1028
|
function applyFixes(result) {
|
|
1032
1029
|
const fixesByFile = /* @__PURE__ */ new Map();
|
|
@@ -1042,7 +1039,7 @@ function applyFixes(result) {
|
|
|
1042
1039
|
let totalFixes = 0;
|
|
1043
1040
|
const filesModified = [];
|
|
1044
1041
|
for (const [filePath, fixes] of fixesByFile) {
|
|
1045
|
-
const content =
|
|
1042
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1046
1043
|
const lines = content.split("\n");
|
|
1047
1044
|
let modified = false;
|
|
1048
1045
|
const sortedFixes = [...fixes].sort((a, b) => b.line - a.line);
|
|
@@ -1060,7 +1057,7 @@ function applyFixes(result) {
|
|
|
1060
1057
|
}
|
|
1061
1058
|
}
|
|
1062
1059
|
if (modified) {
|
|
1063
|
-
|
|
1060
|
+
fs4.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
1064
1061
|
filesModified.push(filePath);
|
|
1065
1062
|
}
|
|
1066
1063
|
}
|
|
@@ -1068,14 +1065,14 @@ function applyFixes(result) {
|
|
|
1068
1065
|
}
|
|
1069
1066
|
|
|
1070
1067
|
// src/core/config.ts
|
|
1071
|
-
import * as
|
|
1068
|
+
import * as fs5 from "fs";
|
|
1072
1069
|
import * as path7 from "path";
|
|
1073
1070
|
var CONFIG_FILENAMES = [".ctxlintrc", ".ctxlintrc.json"];
|
|
1074
1071
|
function loadConfig(projectRoot) {
|
|
1075
1072
|
for (const filename of CONFIG_FILENAMES) {
|
|
1076
1073
|
const filePath = path7.join(projectRoot, filename);
|
|
1077
1074
|
try {
|
|
1078
|
-
const content =
|
|
1075
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1079
1076
|
return JSON.parse(content);
|
|
1080
1077
|
} catch {
|
|
1081
1078
|
continue;
|
|
@@ -1089,11 +1086,11 @@ import * as path8 from "path";
|
|
|
1089
1086
|
|
|
1090
1087
|
// src/version.ts
|
|
1091
1088
|
function loadVersion() {
|
|
1092
|
-
if (true) return "0.2.
|
|
1093
|
-
const
|
|
1089
|
+
if (true) return "0.2.2";
|
|
1090
|
+
const fs6 = __require("fs");
|
|
1094
1091
|
const path9 = __require("path");
|
|
1095
1092
|
const pkgPath = path9.resolve(__dirname, "../package.json");
|
|
1096
|
-
const pkg = JSON.parse(
|
|
1093
|
+
const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
1097
1094
|
return pkg.version;
|
|
1098
1095
|
}
|
|
1099
1096
|
var VERSION = loadVersion();
|
|
@@ -1251,30 +1248,30 @@ Fixed ${fixSummary.totalFixes} issue${fixSummary.totalFixes !== 1 ? "s" : ""} in
|
|
|
1251
1248
|
}
|
|
1252
1249
|
});
|
|
1253
1250
|
program.command("init").description("Set up a git pre-commit hook that runs ctxlint --strict").action(async () => {
|
|
1254
|
-
const
|
|
1251
|
+
const fs6 = await import("fs");
|
|
1255
1252
|
const hooksDir = path8.resolve(".git", "hooks");
|
|
1256
|
-
if (!
|
|
1253
|
+
if (!fs6.existsSync(path8.resolve(".git"))) {
|
|
1257
1254
|
console.error('Error: not a git repository. Run "git init" first.');
|
|
1258
1255
|
process.exit(1);
|
|
1259
1256
|
}
|
|
1260
|
-
if (!
|
|
1261
|
-
|
|
1257
|
+
if (!fs6.existsSync(hooksDir)) {
|
|
1258
|
+
fs6.mkdirSync(hooksDir, { recursive: true });
|
|
1262
1259
|
}
|
|
1263
1260
|
const hookPath = path8.join(hooksDir, "pre-commit");
|
|
1264
1261
|
const hookContent = `#!/bin/sh
|
|
1265
1262
|
# ctxlint pre-commit hook
|
|
1266
1263
|
npx @yawlabs/ctxlint --strict
|
|
1267
1264
|
`;
|
|
1268
|
-
if (
|
|
1269
|
-
const existing =
|
|
1265
|
+
if (fs6.existsSync(hookPath)) {
|
|
1266
|
+
const existing = fs6.readFileSync(hookPath, "utf-8");
|
|
1270
1267
|
if (existing.includes("ctxlint")) {
|
|
1271
1268
|
console.log("Pre-commit hook already includes ctxlint.");
|
|
1272
1269
|
return;
|
|
1273
1270
|
}
|
|
1274
|
-
|
|
1271
|
+
fs6.appendFileSync(hookPath, "\n" + hookContent);
|
|
1275
1272
|
console.log("Added ctxlint to existing pre-commit hook.");
|
|
1276
1273
|
} else {
|
|
1277
|
-
|
|
1274
|
+
fs6.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
1278
1275
|
console.log("Created pre-commit hook at .git/hooks/pre-commit");
|
|
1279
1276
|
}
|
|
1280
1277
|
console.log("ctxlint will now run automatically before each commit.");
|
package/dist/mcp/server.js
CHANGED
|
@@ -18,6 +18,14 @@ import { glob } from "glob";
|
|
|
18
18
|
// src/utils/fs.ts
|
|
19
19
|
import * as fs from "fs";
|
|
20
20
|
import * as path from "path";
|
|
21
|
+
function loadPackageJson(projectRoot) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8");
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
21
29
|
function fileExists(filePath) {
|
|
22
30
|
try {
|
|
23
31
|
fs.accessSync(filePath);
|
|
@@ -93,12 +101,18 @@ var CONTEXT_FILE_PATTERNS = [
|
|
|
93
101
|
".cursor/rules/*.mdc",
|
|
94
102
|
"copilot-instructions.md",
|
|
95
103
|
".github/copilot-instructions.md",
|
|
104
|
+
".github/instructions/*.md",
|
|
96
105
|
".windsurfrules",
|
|
97
106
|
".windsurf/rules/*.md",
|
|
98
107
|
"GEMINI.md",
|
|
99
108
|
"JULES.md",
|
|
100
109
|
".clinerules",
|
|
101
|
-
"CONVENTIONS.md"
|
|
110
|
+
"CONVENTIONS.md",
|
|
111
|
+
"CODEX.md",
|
|
112
|
+
".aiderules",
|
|
113
|
+
".aide/rules/*.md",
|
|
114
|
+
".amazonq/rules/*.md",
|
|
115
|
+
".goose/instructions.md"
|
|
102
116
|
];
|
|
103
117
|
var IGNORED_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor"]);
|
|
104
118
|
async function scanForContextFiles(projectRoot) {
|
|
@@ -551,7 +565,7 @@ function findClosestMatch(target, files) {
|
|
|
551
565
|
// src/core/checks/commands.ts
|
|
552
566
|
import * as fs3 from "fs";
|
|
553
567
|
import * as path4 from "path";
|
|
554
|
-
var NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?)\s+(\S+)/;
|
|
568
|
+
var NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
555
569
|
var MAKE_PATTERN = /^make\s+(\S+)/;
|
|
556
570
|
async function checkCommands(file, projectRoot) {
|
|
557
571
|
const issues = [];
|
|
@@ -574,7 +588,7 @@ async function checkCommands(file, projectRoot) {
|
|
|
574
588
|
}
|
|
575
589
|
continue;
|
|
576
590
|
}
|
|
577
|
-
const shorthandMatch = cmd.match(/^(npm|pnpm|yarn)\s+(test|start|build|dev|lint|format)\b/);
|
|
591
|
+
const shorthandMatch = cmd.match(/^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/);
|
|
578
592
|
if (shorthandMatch && pkgJson) {
|
|
579
593
|
const scriptName = shorthandMatch[2];
|
|
580
594
|
if (pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
@@ -631,14 +645,6 @@ async function checkCommands(file, projectRoot) {
|
|
|
631
645
|
}
|
|
632
646
|
return issues;
|
|
633
647
|
}
|
|
634
|
-
function loadPackageJson(projectRoot) {
|
|
635
|
-
try {
|
|
636
|
-
const content = fs3.readFileSync(path4.join(projectRoot, "package.json"), "utf-8");
|
|
637
|
-
return JSON.parse(content);
|
|
638
|
-
} catch {
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
648
|
function loadMakefile(projectRoot) {
|
|
643
649
|
try {
|
|
644
650
|
return fs3.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
|
|
@@ -755,7 +761,6 @@ function checkAggregateTokens(files) {
|
|
|
755
761
|
}
|
|
756
762
|
|
|
757
763
|
// src/core/checks/redundancy.ts
|
|
758
|
-
import * as fs4 from "fs";
|
|
759
764
|
import * as path6 from "path";
|
|
760
765
|
var PACKAGE_TECH_MAP = {
|
|
761
766
|
react: ["React", "react"],
|
|
@@ -810,7 +815,7 @@ var PACKAGE_TECH_MAP = {
|
|
|
810
815
|
};
|
|
811
816
|
async function checkRedundancy(file, projectRoot) {
|
|
812
817
|
const issues = [];
|
|
813
|
-
const pkgJson =
|
|
818
|
+
const pkgJson = loadPackageJson(projectRoot);
|
|
814
819
|
if (pkgJson) {
|
|
815
820
|
const allDeps = /* @__PURE__ */ new Set([
|
|
816
821
|
...Object.keys(pkgJson.dependencies || {}),
|
|
@@ -905,24 +910,58 @@ function calculateLineOverlap(contentA, contentB) {
|
|
|
905
910
|
}
|
|
906
911
|
return overlap / Math.min(linesA.size, linesB.size);
|
|
907
912
|
}
|
|
908
|
-
function loadPackageJson2(projectRoot) {
|
|
909
|
-
try {
|
|
910
|
-
const content = fs4.readFileSync(path6.join(projectRoot, "package.json"), "utf-8");
|
|
911
|
-
return JSON.parse(content);
|
|
912
|
-
} catch {
|
|
913
|
-
return null;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
913
|
function escapeRegex(str) {
|
|
917
914
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
918
915
|
}
|
|
919
916
|
|
|
917
|
+
// src/core/fixer.ts
|
|
918
|
+
import * as fs4 from "fs";
|
|
919
|
+
import chalk from "chalk";
|
|
920
|
+
function applyFixes(result) {
|
|
921
|
+
const fixesByFile = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const file of result.files) {
|
|
923
|
+
for (const issue of file.issues) {
|
|
924
|
+
if (issue.fix) {
|
|
925
|
+
const existing = fixesByFile.get(issue.fix.file) || [];
|
|
926
|
+
existing.push(issue.fix);
|
|
927
|
+
fixesByFile.set(issue.fix.file, existing);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
let totalFixes = 0;
|
|
932
|
+
const filesModified = [];
|
|
933
|
+
for (const [filePath, fixes] of fixesByFile) {
|
|
934
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
935
|
+
const lines = content.split("\n");
|
|
936
|
+
let modified = false;
|
|
937
|
+
const sortedFixes = [...fixes].sort((a, b) => b.line - a.line);
|
|
938
|
+
for (const fix of sortedFixes) {
|
|
939
|
+
const lineIdx = fix.line - 1;
|
|
940
|
+
if (lineIdx < 0 || lineIdx >= lines.length) continue;
|
|
941
|
+
const line = lines[lineIdx];
|
|
942
|
+
if (line.includes(fix.oldText)) {
|
|
943
|
+
lines[lineIdx] = line.replace(fix.oldText, fix.newText);
|
|
944
|
+
modified = true;
|
|
945
|
+
totalFixes++;
|
|
946
|
+
console.log(
|
|
947
|
+
chalk.green(" Fixed") + ` Line ${fix.line}: ${chalk.dim(fix.oldText)} ${chalk.dim("\u2192")} ${fix.newText}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (modified) {
|
|
952
|
+
fs4.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
953
|
+
filesModified.push(filePath);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return { totalFixes, filesModified };
|
|
957
|
+
}
|
|
958
|
+
|
|
920
959
|
// src/mcp/server.ts
|
|
921
960
|
import * as path7 from "path";
|
|
922
961
|
|
|
923
962
|
// src/version.ts
|
|
924
963
|
function loadVersion() {
|
|
925
|
-
if (true) return "0.2.
|
|
964
|
+
if (true) return "0.2.2";
|
|
926
965
|
const fs5 = __require("fs");
|
|
927
966
|
const path8 = __require("path");
|
|
928
967
|
const pkgPath = path8.resolve(__dirname, "../package.json");
|
|
@@ -1035,6 +1074,47 @@ server.tool(
|
|
|
1035
1074
|
}
|
|
1036
1075
|
}
|
|
1037
1076
|
);
|
|
1077
|
+
server.tool(
|
|
1078
|
+
"ctxlint_fix",
|
|
1079
|
+
"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.",
|
|
1080
|
+
{
|
|
1081
|
+
projectPath: z.string().optional().describe("Path to the project root. Defaults to current working directory."),
|
|
1082
|
+
checks: z.array(z.enum(["paths", "commands", "staleness", "tokens", "redundancy"])).optional().describe("Which checks to run before fixing. Defaults to all.")
|
|
1083
|
+
},
|
|
1084
|
+
async ({ projectPath, checks }) => {
|
|
1085
|
+
const root = path7.resolve(projectPath || process.cwd());
|
|
1086
|
+
const activeChecks = checks || ALL_CHECKS;
|
|
1087
|
+
try {
|
|
1088
|
+
const result = await runAudit(root, activeChecks);
|
|
1089
|
+
const fixSummary = applyFixes(result);
|
|
1090
|
+
return {
|
|
1091
|
+
content: [
|
|
1092
|
+
{
|
|
1093
|
+
type: "text",
|
|
1094
|
+
text: JSON.stringify(
|
|
1095
|
+
{
|
|
1096
|
+
totalFixes: fixSummary.totalFixes,
|
|
1097
|
+
filesModified: fixSummary.filesModified,
|
|
1098
|
+
remainingIssues: result.summary
|
|
1099
|
+
},
|
|
1100
|
+
null,
|
|
1101
|
+
2
|
|
1102
|
+
)
|
|
1103
|
+
}
|
|
1104
|
+
]
|
|
1105
|
+
};
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1108
|
+
return {
|
|
1109
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
1110
|
+
isError: true
|
|
1111
|
+
};
|
|
1112
|
+
} finally {
|
|
1113
|
+
freeEncoder();
|
|
1114
|
+
resetGit();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
);
|
|
1038
1118
|
async function runAudit(projectRoot, activeChecks) {
|
|
1039
1119
|
const discovered = await scanForContextFiles(projectRoot);
|
|
1040
1120
|
const parsed = discovered.map((f) => parseContextFile(f));
|
package/package.json
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/ctxlint",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsup",
|
|
11
|
-
"dev": "tsup --watch",
|
|
12
|
-
"test": "vitest",
|
|
13
|
-
"test:run": "vitest run",
|
|
14
|
-
"lint": "eslint src/",
|
|
15
|
-
"format": "prettier --write src/",
|
|
16
|
-
"mcp": "node dist/mcp/server.js"
|
|
17
|
-
},
|
|
18
9
|
"keywords": [
|
|
19
10
|
"claude",
|
|
20
11
|
"agents",
|
|
@@ -41,7 +32,6 @@
|
|
|
41
32
|
"engines": {
|
|
42
33
|
"node": ">=20"
|
|
43
34
|
},
|
|
44
|
-
"packageManager": "pnpm@10.33.0",
|
|
45
35
|
"dependencies": {
|
|
46
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
37
|
"chalk": "^5.6.2",
|
|
@@ -64,9 +54,13 @@
|
|
|
64
54
|
"typescript-eslint": "^8.58.0",
|
|
65
55
|
"vitest": "^4.1.2"
|
|
66
56
|
},
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"dev": "tsup --watch",
|
|
60
|
+
"test": "vitest",
|
|
61
|
+
"test:run": "vitest run",
|
|
62
|
+
"lint": "eslint src/",
|
|
63
|
+
"format": "prettier --write src/",
|
|
64
|
+
"mcp": "node dist/mcp/server.js"
|
|
71
65
|
}
|
|
72
|
-
}
|
|
66
|
+
}
|