@yawlabs/ctxlint 0.24.0 → 0.25.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/.pre-commit-hooks.yaml +1 -1
- package/AGENT_SESSION_LINT_SPEC.md +35 -3
- package/README.md +17 -4
- package/agent-session-lint-rules.json +10 -0
- package/bin/ctxlint.mjs +360 -225
- package/dist/index.js +265 -57
- package/package.json +1 -1
package/.pre-commit-hooks.yaml
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Version-pinned so a checkout at `rev: vX.Y.Z` runs exactly that release
|
|
5
5
|
# of ctxlint — matches the pinning done by `ctxlint init`. release.sh keeps
|
|
6
6
|
# this in sync with package.json on each bump.
|
|
7
|
-
entry: npx @yawlabs/ctxlint@0.
|
|
7
|
+
entry: npx @yawlabs/ctxlint@0.25.0 --strict
|
|
8
8
|
language: node
|
|
9
9
|
always_run: true
|
|
10
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
|
+
- 13 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
|
|
|
@@ -51,6 +51,7 @@ This is the third pillar alongside context file linting (`CLAUDE.md`, `.cursorru
|
|
|
51
51
|
- [2.10 session/unverified-gate-claimed-clean](#210-sessionunverified-gate-claimed-clean)
|
|
52
52
|
- [2.11 session/default-branch-accumulation](#211-sessiondefault-branch-accumulation)
|
|
53
53
|
- [2.12 session/unresolvable-sha](#212-sessionunresolvable-sha)
|
|
54
|
+
- [2.13 session/large-read](#213-sessionlarge-read)
|
|
54
55
|
- [3. Rule Catalog (machine-readable)](#3-rule-catalog-machine-readable)
|
|
55
56
|
- [4. Implementing This Specification](#4-implementing-this-specification)
|
|
56
57
|
- [5. Contributing](#5-contributing)
|
|
@@ -106,7 +107,7 @@ Session data varies enormously in size and format. This specification targets on
|
|
|
106
107
|
|
|
107
108
|
**What we explicitly DO NOT scan:**
|
|
108
109
|
|
|
109
|
-
- **Full session transcripts** -- too large. Active projects can accumulate hundreds of megabytes of transcript data. Scanning these would be slow and yield low-signal results.
|
|
110
|
+
- **Full session transcripts** -- too large. Active projects can accumulate hundreds of megabytes of transcript data. Scanning these would be slow and yield low-signal results. Rules whose signal is what the agent *did* read a bounded, project-scoped slice instead -- see §3, "Data sources".
|
|
110
111
|
- **SQLite databases** -- Goose's `sessions.db` requires a SQLite dependency. Out of scope for v1. Future versions may add opt-in SQLite support.
|
|
111
112
|
- **File history or shell snapshots** -- some agents capture filesystem state or shell output. These are agent-internal data and not useful for cross-project linting.
|
|
112
113
|
|
|
@@ -132,7 +133,7 @@ Skip hidden directories (starting with `.`) and `node_modules`.
|
|
|
132
133
|
|
|
133
134
|
## 2. Lint Rules
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
13 rules in 1 category (`session`). All rules in this category perform cross-project checks using sibling detection or per-project history analysis.
|
|
136
137
|
|
|
137
138
|
Severity levels:
|
|
138
139
|
- **error** -- the session data reveals a verifiably missing configuration. Should fail CI.
|
|
@@ -459,6 +460,36 @@ Detects a memory that cites a git SHA which no longer resolves in the repository
|
|
|
459
460
|
|
|
460
461
|
---
|
|
461
462
|
|
|
463
|
+
### 2.13 session/large-read
|
|
464
|
+
|
|
465
|
+
Measures how much of a project's session context goes to **whole-file Reads of large files**. It is a baseline, not a defect report.
|
|
466
|
+
|
|
467
|
+
| Field | Value |
|
|
468
|
+
|---|---|
|
|
469
|
+
| **Rule ID** | `session/large-read` |
|
|
470
|
+
| **Severity** | info |
|
|
471
|
+
| **Trigger** | One or more whole-file `Read` calls (no `offset`, `limit` or `pages`) in the project transcript whose result is 4,000 tokens or more |
|
|
472
|
+
| **Message** | `<count> whole-file Read(s) of 4,000+ tokens (<tokens> tokens); est. <carry> tokens of cache-read carry on later turns` |
|
|
473
|
+
| **Source** | Claude Code transcript format; measured corpus (see Notes) |
|
|
474
|
+
|
|
475
|
+
**Detection algorithm:**
|
|
476
|
+
|
|
477
|
+
1. Read the project transcript (see §3, "Data sources"). For each `Read` tool_use, record whether it was partial (`offset`, `limit` or `pages` set), and pair it with its tool_result to count the result's tokens.
|
|
478
|
+
2. Count each session's turns as distinct assistant `message.id`s, falling back to `requestId`, then to one turn per record. Claude Code writes one API response as several records that share an id. Harness-written `<synthetic>` records and sidechain records are not turns. Record the turn count at each `compact_boundary`.
|
|
479
|
+
3. Keep whole-file, non-error Reads whose result is at least 4,000 tokens. A Read that appears twice -- a continued session copies earlier records into its own file under a new session id -- is counted once, by its `tool_use` id.
|
|
480
|
+
4. For each, carry = result tokens × the later turns of its session that re-sent it: from the turn after the Read to the session's last turn or its next compaction, whichever comes first.
|
|
481
|
+
5. When anything qualified, emit ONE info finding per project with the count, total tokens, summed carry, and the top three files by tokens with their read counts. Emit nothing otherwise. When the transcript read was capped, say so in the finding.
|
|
482
|
+
|
|
483
|
+
**Notes:**
|
|
484
|
+
- Why it matters: a tool_result stays in the prompt of every later turn. With prompt caching each re-send bills as a cache read -- cheap per token, but paid per turn for the rest of the session. A large file read whole early in a long session is re-sent hundreds of times, when `grep -n` plus a ranged Read would have carried the few lines needed.
|
|
485
|
+
- It is a baseline for judging read-routing changes against, which is why it is one summary at `info` severity rather than a finding per Read. Reading a file whole is often correct.
|
|
486
|
+
- The threshold: across 739 whole-file Reads in a real corpus of 149 transcripts, Read output (line-number prefixes included) ran at a median 13.5 tokens per line, so 4,000 tokens is roughly 300 lines. An implementation counting with a chars/4 estimate instead measures 12.2 tokens per line on the same corpus, crossing the threshold at roughly 330 lines.
|
|
487
|
+
- Turn counting matters: counting records instead of message ids roughly doubles every carry (one measured transcript held 1,338 assistant records for 640 ids), and ignoring compaction overstates any session that compacted (25 of 150 transcripts in the same corpus did).
|
|
488
|
+
- The carry is an estimate. Tokens come from a proxy tokenizer -- cl100k where the implementation can load one, a chars/4 approximation otherwise, which is what the reference implementation's published bundle uses -- a result's first re-send is a cache write rather than a read, and a Read copied into a continued session is counted with the smaller of its two carries. Figures are not converted to cost, since token prices vary by model and change.
|
|
489
|
+
- Remediation: find the region with `grep -n` (or the Grep tool) and Read it with `offset`/`limit`, or delegate the whole-file question to a subagent, whose reads stay in its own context.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
462
493
|
## 3. Rule Catalog (machine-readable)
|
|
463
494
|
|
|
464
495
|
A machine-readable JSON catalog of all rules is available at [`agent-session-lint-rules.json`](./agent-session-lint-rules.json). It conforms to the shared catalog schema ([`schemas/ctxlint-catalog.schema.json`](./schemas/ctxlint-catalog.schema.json)) used by all four pillars: each rule entry carries `id`, `category`, `severity`, `description`, `trigger`, `message`, `fixable`, and `stability`, plus rule-specific extras (e.g. `canonicalFiles` on `session/diverged-file`).
|
|
@@ -483,6 +514,7 @@ Catalog rule IDs use the pillar-stable `session/<slug>` form -- these are the cr
|
|
|
483
514
|
| `session/unverified-gate-claimed-clean` | `session-unverified-gate-claimed-clean/unverified-gate-claimed-clean` |
|
|
484
515
|
| `session/default-branch-accumulation` | `session-default-branch-accumulation/default-branch-accumulation` |
|
|
485
516
|
| `session/unresolvable-sha` | `session-unresolvable-sha/unresolvable-sha` |
|
|
517
|
+
| `session/large-read` | `session-large-read/large-read` |
|
|
486
518
|
|
|
487
519
|
### Data sources: history vs. transcript
|
|
488
520
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://github.com/YawLabs/ctxlint/stargazers)
|
|
6
6
|
[](https://github.com/YawLabs/mcp-compliance)
|
|
7
|
+
[](https://x.com/TokenLimitNews)
|
|
7
8
|
|
|
8
9
|
**Lint your AI agent context files, MCP server configs, and session data against your actual codebase.** Context linting + MCP config linting + session auditing. 16 AI tools, 8 MCP clients, cross-project consistency, auto-fix. Works as a CLI, CI step, pre-commit hook, or MCP server.
|
|
9
10
|
|
|
@@ -89,6 +90,11 @@ Useful if you want `ctxlint` available in every project without per-project setu
|
|
|
89
90
|
| **Duplicate memory** | Near-duplicate memories across projects (>60% content overlap) |
|
|
90
91
|
| **Loop detection** | Agent stuck in loops — repeated commands or cyclic patterns in session history |
|
|
91
92
|
| **Memory overflow** | `MEMORY.md` past Claude Code's 200-line / 25KB session-load cap — entries beyond it are invisible to the agent |
|
|
93
|
+
| **Shared temp path** | A fixed temp path (e.g. `/tmp/pkg.bak`) the agent writes and later reads back — any concurrent session can overwrite it in between |
|
|
94
|
+
| **Unverified gate** | A lint/typecheck/test/build run that errored or printed nothing, followed by agent prose claiming it passed |
|
|
95
|
+
| **Default-branch edits** | 10+ files edited on `main`/`master` with no intervening commit or branch-away |
|
|
96
|
+
| **Unresolvable SHA** | A memory cites a commit SHA that does not resolve in this repository |
|
|
97
|
+
| **Large reads** | Whole-file Reads of 4,000+ tokens, with an estimate of the tokens they re-send as cached context on later turns (an info-level baseline) |
|
|
92
98
|
|
|
93
99
|
## Supported Context Files
|
|
94
100
|
|
|
@@ -201,6 +207,8 @@ Session checks are **opt-in** because they access files outside the project dire
|
|
|
201
207
|
| Claude Code | `~/.claude/history.jsonl` | `~/.claude/projects/*/memory/*.md` |
|
|
202
208
|
| Codex CLI | `~/.codex/history.jsonl` | — |
|
|
203
209
|
|
|
210
|
+
Checks whose signal is what the agent did (commands run, files written or read) also read the current project's Claude Code session transcripts, `~/.claude/projects/<encoded-project>/*.jsonl`, bounded to the 5 most recent.
|
|
211
|
+
|
|
204
212
|
### What session checks catch
|
|
205
213
|
|
|
206
214
|
| Check | What it finds |
|
|
@@ -212,6 +220,11 @@ Session checks are **opt-in** because they access files outside the project dire
|
|
|
212
220
|
| **Duplicate memory** | Near-duplicate memory entries across projects (>60% overlap) |
|
|
213
221
|
| **Loop detection** | Agent stuck in a loop — 3+ consecutive identical commands, or cyclic A,B,A,B patterns |
|
|
214
222
|
| **Memory index overflow** | `MEMORY.md` exceeds Claude Code's documented 200-line / 25KB session-load cap, so entries past the cap are invisible to the agent |
|
|
223
|
+
| **Shared temp path** | A fixed temp path (e.g. `/tmp/pkg.bak`) the agent writes and later reads back — any concurrent session can overwrite it in between |
|
|
224
|
+
| **Unverified gate** | A lint/typecheck/test/build run that errored or printed nothing, followed by agent prose claiming it passed |
|
|
225
|
+
| **Default-branch edits** | 10+ files edited on `main`/`master` with no intervening commit or branch-away |
|
|
226
|
+
| **Unresolvable SHA** | A memory cites a commit SHA that does not resolve in this repository |
|
|
227
|
+
| **Large reads** | Whole-file Reads of 4,000+ tokens, with an estimate of the tokens they re-send as cached context on later turns (an info-level baseline) |
|
|
215
228
|
|
|
216
229
|
### Session Linting Specification
|
|
217
230
|
|
|
@@ -221,7 +234,7 @@ Session checks are **opt-in** because they access files outside the project dire
|
|
|
221
234
|
## Example Output
|
|
222
235
|
|
|
223
236
|
```
|
|
224
|
-
ctxlint v0.
|
|
237
|
+
ctxlint v0.25.0
|
|
225
238
|
|
|
226
239
|
Scanning /Users/you/my-app...
|
|
227
240
|
|
|
@@ -282,7 +295,7 @@ Commands:
|
|
|
282
295
|
init Set up a git pre-commit hook
|
|
283
296
|
```
|
|
284
297
|
|
|
285
|
-
**Available checks:** `paths`, `commands`, `staleness`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `ci-coverage`, `ci-secrets`, `content-secrets`, `hook-coverage`, `mcp-schema`, `mcp-security`, `mcp-commands`, `mcp-deprecated`, `mcp-env`, `mcp-urls`, `mcp-consistency`, `mcp-redundancy`, `session-missing-secret`, `session-diverged-file`, `session-missing-workflow`, `session-stale-memory`, `session-duplicate-memory`, `session-loop-detection`, `session-memory-index-overflow`, `skill-frontmatter`, `skill-broken-ref`, `skill-trigger-collision`, `skill-orphaned`, `skill-dead-tool-restriction`
|
|
298
|
+
**Available checks:** `paths`, `commands`, `staleness`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `ci-coverage`, `ci-secrets`, `content-secrets`, `hook-coverage`, `mcp-schema`, `mcp-security`, `mcp-commands`, `mcp-deprecated`, `mcp-env`, `mcp-urls`, `mcp-consistency`, `mcp-redundancy`, `session-missing-secret`, `session-diverged-file`, `session-missing-workflow`, `session-stale-memory`, `session-duplicate-memory`, `session-loop-detection`, `session-memory-index-overflow`, `session-shared-temp-path`, `session-unverified-gate-claimed-clean`, `session-default-branch-accumulation`, `session-unresolvable-sha`, `session-large-read`, `skill-frontmatter`, `skill-broken-ref`, `skill-trigger-collision`, `skill-orphaned`, `skill-dead-tool-restriction`
|
|
286
299
|
|
|
287
300
|
Passing any `mcp-*` check name implies `--mcp`. Passing any `session-*` check name implies `--session`. Passing any `skill-*` check name implies `--skills`.
|
|
288
301
|
|
|
@@ -364,7 +377,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
364
377
|
```yaml
|
|
365
378
|
repos:
|
|
366
379
|
- repo: https://github.com/yawlabs/ctxlint
|
|
367
|
-
rev: v0.
|
|
380
|
+
rev: v0.25.0
|
|
368
381
|
hooks:
|
|
369
382
|
- id: ctxlint
|
|
370
383
|
```
|
|
@@ -519,7 +532,7 @@ ctxlint is the reference implementation of four open specifications for linting
|
|
|
519
532
|
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
520
533
|
| **[AI Context File Linting Spec](./CONTEXT_LINT_SPEC.md)** | 41 rules for validating context files (CLAUDE.md, .cursorrules, AGENTS.md, etc.) across 16 clients. Covers file formats, frontmatter schemas, path/command validation, staleness, token budgets, redundancy, and contradictions. |
|
|
521
534
|
| **[MCP Config Linting Spec](./MCP_CONFIG_LINT_SPEC.md)** | 29 rules for validating MCP server configs (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, etc.) across 8 clients. Covers schema validation, hardcoded secrets, env var syntax, deprecated transports, and cross-file consistency. |
|
|
522
|
-
| **[Agent Session Linting Spec](./AGENT_SESSION_LINT_SPEC.md)** |
|
|
535
|
+
| **[Agent Session Linting Spec](./AGENT_SESSION_LINT_SPEC.md)** | 13 rules for auditing agent session data (history, memory) across 8 agents. Covers cross-project secret consistency, config drift, stale memory, and loop detection. |
|
|
523
536
|
| **[Agent Skill Linting Spec](./AGENT_SKILL_LINT_SPEC.md)** | 5 rules for auditing Claude Code skill (`SKILL.md`) and agent (`.md`) definitions under `~/.claude`. Covers frontmatter presence, broken refs, trigger-phrase collisions, orphaned skills, and dead tool restrictions. (v1, experimental) |
|
|
524
537
|
|
|
525
538
|
All specs include machine-readable rule catalogs for programmatic consumption:
|
|
@@ -144,6 +144,16 @@
|
|
|
144
144
|
"message": "cited commit {sha} does not resolve in this repository",
|
|
145
145
|
"fixable": false,
|
|
146
146
|
"stability": "experimental"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "session/large-read",
|
|
150
|
+
"category": "session",
|
|
151
|
+
"severity": "info",
|
|
152
|
+
"description": "Measures session context spent on whole-file Reads of large files. Each result is re-sent as prompt context (cache reads) on every later turn of its session; the finding is a baseline for judging read-routing changes, reported as one summary per project.",
|
|
153
|
+
"trigger": "One or more whole-file Read calls (no offset, limit or pages) in the current project's transcripts whose result is 4,000 tokens or more. Carry is each result's tokens times the later turns of its session that re-sent it, up to the session's end or its next /compact boundary; a turn is a distinct assistant message id.",
|
|
154
|
+
"message": "{count} whole-file Reads of 4,000+ tokens ({tokens} tokens); est. {carry} tokens of cache-read carry on later turns",
|
|
155
|
+
"fixable": false,
|
|
156
|
+
"stability": "experimental"
|
|
147
157
|
}
|
|
148
158
|
],
|
|
149
159
|
"dataSources": [
|