@yawlabs/ctxlint 0.9.10 → 0.9.11
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/.pre-commit-hooks.yaml +4 -1
- package/AGENT_SESSION_LINT_SPEC.md +1 -1
- package/MCP_CONFIG_LINT_SPEC.md +1 -1
- package/README.md +3 -3
- package/dist/index.js +54 -23
- package/package.json +2 -2
package/.pre-commit-hooks.yaml
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
- id: ctxlint
|
|
2
2
|
name: ctxlint
|
|
3
3
|
description: Lint AI agent context files against your actual codebase
|
|
4
|
-
|
|
4
|
+
# Version-pinned so a checkout at `rev: vX.Y.Z` runs exactly that release
|
|
5
|
+
# of ctxlint — matches the pinning done by `ctxlint init`. release.sh keeps
|
|
6
|
+
# this in sync with package.json on each bump.
|
|
7
|
+
entry: npx @yawlabs/ctxlint@0.9.11 --strict
|
|
5
8
|
language: node
|
|
6
9
|
always_run: true
|
|
7
10
|
pass_filenames: false
|
|
@@ -15,7 +15,7 @@ This specification defines a standard set of lint rules for validating agent ses
|
|
|
15
15
|
|
|
16
16
|
The specification includes:
|
|
17
17
|
- A reference of session data locations across 8 AI coding agents
|
|
18
|
-
-
|
|
18
|
+
- 7 lint rules in the `session` category with defined severities
|
|
19
19
|
- A machine-readable rule catalog ([`agent-session-lint-rules.json`](./agent-session-lint-rules.json))
|
|
20
20
|
- Sibling-repo detection for cross-project checks
|
|
21
21
|
|
package/MCP_CONFIG_LINT_SPEC.md
CHANGED
|
@@ -16,7 +16,7 @@ This specification defines a standard set of lint rules for validating MCP serve
|
|
|
16
16
|
|
|
17
17
|
The specification includes:
|
|
18
18
|
- A complete reference of MCP config file locations, formats, and client-specific behaviors
|
|
19
|
-
-
|
|
19
|
+
- 43 lint rules organized into 8 categories with defined severities
|
|
20
20
|
- A machine-readable rule catalog ([`mcp-config-lint-rules.json`](./mcp-config-lint-rules.json))
|
|
21
21
|
- Auto-fix definitions for rules that support automated correction
|
|
22
22
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Multiply that across a team with 5 context files, 3 MCP configs, and 2 people wh
|
|
|
20
20
|
|
|
21
21
|
ctxlint is a linter purpose-built for this. It reads your context files, cross-references them against your actual codebase, and catches the drift before your agent does.
|
|
22
22
|
|
|
23
|
-
- **Instant startup** — ships as a single self-contained bundle with zero runtime dependencies. `npx` downloads ~
|
|
23
|
+
- **Instant startup** — ships as a single self-contained bundle with zero runtime dependencies. `npx` downloads a ~400 KB tarball and starts immediately
|
|
24
24
|
- **Catches real problems** — broken paths, wrong commands, stale references, contradictions across files
|
|
25
25
|
- **Smart suggestions** — detects git renames and fuzzy-matches to suggest the right path
|
|
26
26
|
- **Auto-fix** — `--fix` rewrites broken paths automatically using git history
|
|
@@ -215,7 +215,7 @@ Session checks are **opt-in** because they access files outside the project dire
|
|
|
215
215
|
## Example Output
|
|
216
216
|
|
|
217
217
|
```
|
|
218
|
-
ctxlint v0.9.
|
|
218
|
+
ctxlint v0.9.10
|
|
219
219
|
|
|
220
220
|
Scanning /Users/you/my-app...
|
|
221
221
|
|
|
@@ -351,7 +351,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
351
351
|
```yaml
|
|
352
352
|
repos:
|
|
353
353
|
- repo: https://github.com/yawlabs/ctxlint
|
|
354
|
-
rev: v0.9.
|
|
354
|
+
rev: v0.9.10
|
|
355
355
|
hooks:
|
|
356
356
|
- id: ctxlint
|
|
357
357
|
```
|
package/dist/index.js
CHANGED
|
@@ -34559,7 +34559,7 @@ function parseSections(lines) {
|
|
|
34559
34559
|
if (sections.length > 0) {
|
|
34560
34560
|
const prev = sections[sections.length - 1];
|
|
34561
34561
|
if (prev.endLine === -1) {
|
|
34562
|
-
prev.endLine = i2
|
|
34562
|
+
prev.endLine = i2;
|
|
34563
34563
|
}
|
|
34564
34564
|
}
|
|
34565
34565
|
sections.push({
|
|
@@ -41404,10 +41404,16 @@ function loadSettingsSources(projectRoot) {
|
|
|
41404
41404
|
path6.join(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
|
|
41405
41405
|
];
|
|
41406
41406
|
for (const p2 of candidates) {
|
|
41407
|
+
let content;
|
|
41407
41408
|
try {
|
|
41408
|
-
|
|
41409
|
-
sources.push(JSON.parse(content));
|
|
41409
|
+
content = fs5.readFileSync(p2, "utf-8");
|
|
41410
41410
|
} catch {
|
|
41411
|
+
continue;
|
|
41412
|
+
}
|
|
41413
|
+
try {
|
|
41414
|
+
sources.push(JSON.parse(content));
|
|
41415
|
+
} catch (err) {
|
|
41416
|
+
console.warn(`ctxlint: could not parse ${p2}: ${err.message}`);
|
|
41411
41417
|
}
|
|
41412
41418
|
}
|
|
41413
41419
|
return sources;
|
|
@@ -43845,7 +43851,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
43845
43851
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
43846
43852
|
import { fileURLToPath } from "node:url";
|
|
43847
43853
|
function loadVersion() {
|
|
43848
|
-
if (true) return "0.9.
|
|
43854
|
+
if (true) return "0.9.11";
|
|
43849
43855
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
43850
43856
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
43851
43857
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
@@ -44721,7 +44727,7 @@ function applyFixes(result, options = {}) {
|
|
|
44721
44727
|
let line = lines[lineIdx];
|
|
44722
44728
|
for (const fix of lineFixes) {
|
|
44723
44729
|
if (line.includes(fix.oldText)) {
|
|
44724
|
-
line = line.
|
|
44730
|
+
line = line.replaceAll(fix.oldText, fix.newText);
|
|
44725
44731
|
totalFixes++;
|
|
44726
44732
|
const prefix = dryRun ? source_default.cyan(" Would fix") : source_default.green(" Fixed");
|
|
44727
44733
|
log(
|
|
@@ -44917,8 +44923,11 @@ var init_server3 = __esm({
|
|
|
44917
44923
|
checks: external_exports3.array(checkEnum).optional().describe("Which checks to run before fixing. Defaults to all.")
|
|
44918
44924
|
},
|
|
44919
44925
|
{
|
|
44926
|
+
// ctxlint_fix writes to disk via applyFixes() → fs.writeFileSync, so it
|
|
44927
|
+
// must advertise destructiveHint: true. Hosts (Claude Code, Cursor, etc.)
|
|
44928
|
+
// use this to decide whether to require user confirmation before the call.
|
|
44920
44929
|
readOnlyHint: false,
|
|
44921
|
-
destructiveHint:
|
|
44930
|
+
destructiveHint: true,
|
|
44922
44931
|
idempotentHint: true,
|
|
44923
44932
|
openWorldHint: false
|
|
44924
44933
|
},
|
|
@@ -51627,6 +51636,9 @@ function formatTokenReport(result) {
|
|
|
51627
51636
|
lines.push("");
|
|
51628
51637
|
return lines.join("\n");
|
|
51629
51638
|
}
|
|
51639
|
+
function isSyntheticPath(p2) {
|
|
51640
|
+
return p2.startsWith("(") || p2.startsWith("~");
|
|
51641
|
+
}
|
|
51630
51642
|
function formatSarif(result) {
|
|
51631
51643
|
const severityToLevel = {
|
|
51632
51644
|
error: "error",
|
|
@@ -51636,25 +51648,31 @@ function formatSarif(result) {
|
|
|
51636
51648
|
const results = [];
|
|
51637
51649
|
for (const file2 of result.files) {
|
|
51638
51650
|
for (const issue2 of file2.issues) {
|
|
51651
|
+
const location = isSyntheticPath(file2.path) ? {
|
|
51652
|
+
logicalLocations: [
|
|
51653
|
+
{
|
|
51654
|
+
name: file2.path,
|
|
51655
|
+
kind: "resource"
|
|
51656
|
+
}
|
|
51657
|
+
]
|
|
51658
|
+
} : {
|
|
51659
|
+
physicalLocation: {
|
|
51660
|
+
artifactLocation: {
|
|
51661
|
+
uri: file2.path,
|
|
51662
|
+
uriBaseId: "%SRCROOT%"
|
|
51663
|
+
},
|
|
51664
|
+
region: {
|
|
51665
|
+
startLine: Math.max(issue2.line, 1)
|
|
51666
|
+
}
|
|
51667
|
+
}
|
|
51668
|
+
};
|
|
51639
51669
|
const sarifResult = {
|
|
51640
51670
|
ruleId: `ctxlint/${issue2.check}`,
|
|
51641
51671
|
level: severityToLevel[issue2.severity] || "note",
|
|
51642
51672
|
message: {
|
|
51643
51673
|
text: issue2.message + (issue2.suggestion ? ` (${issue2.suggestion})` : "")
|
|
51644
51674
|
},
|
|
51645
|
-
locations: [
|
|
51646
|
-
{
|
|
51647
|
-
physicalLocation: {
|
|
51648
|
-
artifactLocation: {
|
|
51649
|
-
uri: file2.path,
|
|
51650
|
-
uriBaseId: "%SRCROOT%"
|
|
51651
|
-
},
|
|
51652
|
-
region: {
|
|
51653
|
-
startLine: Math.max(issue2.line, 1)
|
|
51654
|
-
}
|
|
51655
|
-
}
|
|
51656
|
-
}
|
|
51657
|
-
]
|
|
51675
|
+
locations: [location]
|
|
51658
51676
|
};
|
|
51659
51677
|
if (issue2.detail) {
|
|
51660
51678
|
sarifResult.message.text += `
|
|
@@ -52174,6 +52192,7 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
52174
52192
|
path11.join(resolvedPath, ".continue"),
|
|
52175
52193
|
path11.join(resolvedPath, ".vscode")
|
|
52176
52194
|
];
|
|
52195
|
+
const watchers = [];
|
|
52177
52196
|
let debounceTimer = null;
|
|
52178
52197
|
const rerun = () => {
|
|
52179
52198
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -52215,16 +52234,28 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
52215
52234
|
};
|
|
52216
52235
|
for (const filePath of watchPaths) {
|
|
52217
52236
|
try {
|
|
52218
|
-
fs9.watch(filePath, rerun);
|
|
52237
|
+
watchers.push(fs9.watch(filePath, rerun));
|
|
52219
52238
|
} catch {
|
|
52220
52239
|
}
|
|
52221
52240
|
}
|
|
52222
52241
|
for (const dir of watchDirs) {
|
|
52223
52242
|
try {
|
|
52224
|
-
fs9.watch(dir, { recursive: true }, rerun);
|
|
52243
|
+
watchers.push(fs9.watch(dir, { recursive: true }, rerun));
|
|
52225
52244
|
} catch {
|
|
52226
52245
|
}
|
|
52227
52246
|
}
|
|
52247
|
+
const shutdown = () => {
|
|
52248
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
52249
|
+
for (const w of watchers) {
|
|
52250
|
+
try {
|
|
52251
|
+
w.close();
|
|
52252
|
+
} catch {
|
|
52253
|
+
}
|
|
52254
|
+
}
|
|
52255
|
+
process.exit(0);
|
|
52256
|
+
};
|
|
52257
|
+
process.once("SIGINT", shutdown);
|
|
52258
|
+
process.once("SIGTERM", shutdown);
|
|
52228
52259
|
await new Promise(() => {
|
|
52229
52260
|
});
|
|
52230
52261
|
}
|
|
@@ -52243,11 +52274,11 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
52243
52274
|
const hookPath = path11.join(hooksDir, "pre-commit");
|
|
52244
52275
|
const fullHookContent = `#!/bin/sh
|
|
52245
52276
|
# ctxlint pre-commit hook
|
|
52246
|
-
npx @yawlabs/ctxlint --strict
|
|
52277
|
+
npx @yawlabs/ctxlint@${VERSION} --strict
|
|
52247
52278
|
`;
|
|
52248
52279
|
const appendHookContent = `
|
|
52249
52280
|
# ctxlint pre-commit hook
|
|
52250
|
-
npx @yawlabs/ctxlint --strict
|
|
52281
|
+
npx @yawlabs/ctxlint@${VERSION} --strict
|
|
52251
52282
|
`;
|
|
52252
52283
|
if (fs9.existsSync(hookPath)) {
|
|
52253
52284
|
const existing = fs9.readFileSync(hookPath, "utf-8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/ctxlint",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.11",
|
|
4
4
|
"description": "Lint your AI agent context files, MCP server configs, and session data against your actual codebase",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ctxlint": "dist/index.js"
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"esbuild"
|
|
104
104
|
],
|
|
105
105
|
"overrides": {
|
|
106
|
-
"hono": "^4.12.
|
|
106
|
+
"hono": "^4.12.14",
|
|
107
107
|
"@hono/node-server": "^1.19.13",
|
|
108
108
|
"vite": "^8.0.8"
|
|
109
109
|
}
|