@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.
@@ -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
- entry: npx @yawlabs/ctxlint --strict
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
- - 5 lint rules in the `session` category with defined severities
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
 
@@ -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
- - 27 lint rules organized into 8 categories with defined severities
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 ~200 KB and starts immediately
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.0
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.0
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 - 1;
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
- const content = fs5.readFileSync(p2, "utf-8");
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.10";
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.replace(fix.oldText, fix.newText);
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: false,
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.10",
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.12",
106
+ "hono": "^4.12.14",
107
107
  "@hono/node-server": "^1.19.13",
108
108
  "vite": "^8.0.8"
109
109
  }